<commit_before>// Spin
// --------------------------------------------------

// Global variables

var Server = function() {
	this.clicks = 1;			// timer
	this.debug = false;
	this.paused = false;
	this.gameOver = false;
	this.win = false;
	this.timeLeft;
	this.lightsOn = false;
	this.firstPerson = true;
	this.swoopMode = false;

	this.b2world;				// Box2d world
	this.canvas;				// HTML canvas
	this.c;						// Context

	this.renderer;				// THREE.js
	this.camera;
	this.scene;

	this.player;
	this.obstacles;
	this.world;
	this.utils;
	this.cameraRotation = 0;
	this.targetRotation = 0;

	this.settings = {
		'player': {
			'initialX': 0,		// 0
			'initialY': 5.5,	// 5.5
			'initialZ': 1,
			'initialAngle': 0,
			'size': 0.5,
			'maxHealth': 1000,
			'density': (this.firstPerson) ? 20 : 8,
			'friction': 0.1,
			'restitution': 0.1,
			'linearDamping': (this.firstPerson) ? 2 : 0,
			'angularDamping': 4.0,
			'maxAngularVelocity': 2,
			'turnSpeed': 150,
			'thrustAmount': 10,
			'maxVelocity': 5,
		},
		'game': {
			'timeLeft': 600,			// in seconds (10 minutes)
		},
		'screen': {
			'width': (this.firstPerson) ? 900 : 600,
			'height': 600,
			'fov': (this.firstPerson) ? 60 : 45,
			'aspect': (this.firstPerson) ? 900 / 600 : 600 / 600,
			'near': 0.1,
			'far': 40,
		},
		'world': {
			'gravity': 2,
			'scale': 25,
		},
		'camera': {
			'step': 0.1,
		},
		'wall': {
			'depth': 7,
			'density': 1,
			'friction': 0,
			'restitution': 0.9,
		},
		'obstacle': {
			'size': .4,
			'width': .85,
			'height': .85,
			'depth': 2,
			'density': 1,
			'friction': 0,
			'restitution': 1,
		},
		'damage': {
			'hitLength': 15,
			'wall': 3,
			'floater': 5,
			'obstacle': 10,
		},
		'exit': {
			'size': 1.5,
			'topradius': 1.25,
			'bottomradius': 1.75,
			'height': .25,
			'density': 5,
			'friction': 1,
			'restitution': 0,
		},
	};

	this.keys = {};
	this.debugDraw;

	this.gravities = [
		[ 0, this.settings.world.gravity ],
		[ this.settings.world.gravity, 0 ],
		[ 0, -this.settings.world.gravity ],
		[ -this.settings.world.gravity, 0 ]
	];
	this.rotations = [ 0, Math.PI / 2, Math.PI, 3 * Math.PI / 2 ];
	this.rotIndex = 0;

	this.update = function() {
		if (!server.paused && !server.gameOver && !server.win) {
			// Run physics
			server.b2world.Step(server.b2stepAmount, 10, 10);

			// Update player location
			server.player.x = server.player.body.GetPosition().x;
			server.player.y = server.player.body.GetPosition().y;
			server.player.angle = server.player.body.GetAngle();

			// Update obstacle locations
			for (var i=0; i<server.obstacles.length; i++) {
				var o = server.obstacles[i];

				o.x = o.body.GetPosition().x;
				o.y = o.body.GetPosition().y;
				o.angle = o.body.GetAngle();
			}

			// Every so often, rotate the world (but not in first-person mode)
			if (server.clicks % 300 == 0 && !server.firstPerson) {
				server.rotateWorld();
			}

			// Drop the player's hit counter
			if (server.player.lastHitCounter > 0) {
				server.player.lastHitCounter--;
			}

			// Drop the time left counter
			if (server.clicks % 60 == 0) {
				server.timeLeft--;
			}

			// Draw
			if (server.debug) {
				server.b2world.DrawDebugData();
			}
			server.display.render();

			// Clear
			server.b2world.ClearForces();

			// Update clicks
			server.clicks++;

			// If player health is 0 or time has run out, game over
			if (server.player.health <= 0 || server.timeLeft <= 0) {
				server.gameOver = true;
			}
		}

		// Game over
		if (server.gameOver) {
			server.display.gameOver();
		}

		// Win
		if (server.win) {
			server.display.win();
		}

		if (!server.gameOver && !server.win) {
			requestAnimFrame(server.update);
		}
	};

	this.collision = function() {
		this.listener = new b2ContactListener();

		this.listener.PostSolve = function(contact, impulse) {
			var bodyA = contact.GetFixtureA().GetBody().GetUserData();
			var bodyB = contact.GetFixtureB().GetBody().GetUserData();

			// Player collisions
			if (bodyA.type == 'player' || bodyB.type == 'player') {
				var playerBody = (bodyA.type == 'player') ? bodyA : bodyB;
				var otherBody = (bodyA.type == 'player') ? bodyB : bodyA;

				if (otherBody.type == 'wall') {
					server.player.health -= server.settings.damage.wall;

					server.updateHealthDisplay();
					server.player.lastHitCounter += server.settings.damage.hitLength;
				} else if (otherBody.type == 'obstacle') {
					server.player.health -= server.settings.damage.obstacle;

					server.updateHealthDisplay();
					server.player.lastHitCounter += server.settings.damage.hitLength;
				} else if (otherBody.type == 'exit') {
					server.win = true;
				}
			} else {
				// Other collisions
			}
		};

		this.b2world.SetContactListener(this.listener);
	};

	this.rotateWorld = function() {
		this.rotIndex++;
		if (this.rotIndex > this.gravities.length - 1) this.rotIndex = 0;
		var grav = this.gravities[this.rotIndex];

		// Change gravity
		server.b2world.SetGravity(new b2Vec2(grav[0], grav[1]));

		// And set the target rotation
		server.targetRotation = this.rotations[this.rotIndex];
	};

	this.updateHealthDisplay = function() {
		var healthWidth = Math.floor((server.player.health / 1000) * $("#status").width());

		$("#status .health").width(healthWidth);
	};
};
<commit_msg>Deactivated first-person mode for the initial config<commit_after>// Spin
// --------------------------------------------------

// Global variables

var Server = function() {
	this.clicks = 1;			// timer
	this.debug = false;
	this.paused = false;
	this.gameOver = false;
	this.win = false;
	this.timeLeft;
	this.lightsOn = false;
	this.firstPerson = false;
	this.swoopMode = false;

	this.b2world;				// Box2d world
	this.canvas;				// HTML canvas
	this.c;						// Context

	this.renderer;				// THREE.js
	this.camera;
	this.scene;

	this.player;
	this.obstacles;
	this.world;
	this.utils;
	this.cameraRotation = 0;
	this.targetRotation = 0;

	this.settings = {
		'player': {
			'initialX': 0,		// 0
			'initialY': 5.5,	// 5.5
			'initialZ': 1,
			'initialAngle': 0,
			'size': 0.5,
			'maxHealth': 1000,
			'density': (this.firstPerson) ? 20 : 8,
			'friction': 0.1,
			'restitution': 0.1,
			'linearDamping': (this.firstPerson) ? 2 : 0,
			'angularDamping': 4.0,
			'maxAngularVelocity': 2,
			'turnSpeed': 150,
			'thrustAmount': 10,
			'maxVelocity': 5,
		},
		'game': {
			'timeLeft': 600,			// in seconds (10 minutes)
		},
		'screen': {
			'width': (this.firstPerson) ? 900 : 600,
			'height': 600,
			'fov': (this.firstPerson) ? 60 : 45,
			'aspect': (this.firstPerson) ? 900 / 600 : 600 / 600,
			'near': 0.1,
			'far': 40,
		},
		'world': {
			'gravity': 2,
			'scale': 25,
		},
		'camera': {
			'step': 0.1,
		},
		'wall': {
			'depth': 7,
			'density': 1,
			'friction': 0,
			'restitution': 0.9,
		},
		'obstacle': {
			'size': .4,
			'width': .85,
			'height': .85,
			'depth': 2,
			'density': 1,
			'friction': 0,
			'restitution': 1,
		},
		'damage': {
			'hitLength': 15,
			'wall': 3,
			'floater': 5,
			'obstacle': 10,
		},
		'exit': {
			'size': 1.5,
			'topradius': 1.25,
			'bottomradius': 1.75,
			'height': .25,
			'density': 5,
			'friction': 1,
			'restitution': 0,
		},
	};

	this.keys = {};
	this.debugDraw;

	this.gravities = [
		[ 0, this.settings.world.gravity ],
		[ this.settings.world.gravity, 0 ],
		[ 0, -this.settings.world.gravity ],
		[ -this.settings.world.gravity, 0 ]
	];
	this.rotations = [ 0, Math.PI / 2, Math.PI, 3 * Math.PI / 2 ];
	this.rotIndex = 0;

	this.update = function() {
		if (!server.paused && !server.gameOver && !server.win) {
			// Run physics
			server.b2world.Step(server.b2stepAmount, 10, 10);

			// Update player location
			server.player.x = server.player.body.GetPosition().x;
			server.player.y = server.player.body.GetPosition().y;
			server.player.angle = server.player.body.GetAngle();

			// Update obstacle locations
			for (var i=0; i<server.obstacles.length; i++) {
				var o = server.obstacles[i];

				o.x = o.body.GetPosition().x;
				o.y = o.body.GetPosition().y;
				o.angle = o.body.GetAngle();
			}

			// Every so often, rotate the world (but not in first-person mode)
			if (server.clicks % 300 == 0 && !server.firstPerson) {
				server.rotateWorld();
			}

			// Drop the player's hit counter
			if (server.player.lastHitCounter > 0) {
				server.player.lastHitCounter--;
			}

			// Drop the time left counter
			if (server.clicks % 60 == 0) {
				server.timeLeft--;
			}

			// Draw
			if (server.debug) {
				server.b2world.DrawDebugData();
			}
			server.display.render();

			// Clear
			server.b2world.ClearForces();

			// Update clicks
			server.clicks++;

			// If player health is 0 or time has run out, game over
			if (server.player.health <= 0 || server.timeLeft <= 0) {
				server.gameOver = true;
			}
		}

		// Game over
		if (server.gameOver) {
			server.display.gameOver();
		}

		// Win
		if (server.win) {
			server.display.win();
		}

		if (!server.gameOver && !server.win) {
			requestAnimFrame(server.update);
		}
	};

	this.collision = function() {
		this.listener = new b2ContactListener();

		this.listener.PostSolve = function(contact, impulse) {
			var bodyA = contact.GetFixtureA().GetBody().GetUserData();
			var bodyB = contact.GetFixtureB().GetBody().GetUserData();

			// Player collisions
			if (bodyA.type == 'player' || bodyB.type == 'player') {
				var playerBody = (bodyA.type == 'player') ? bodyA : bodyB;
				var otherBody = (bodyA.type == 'player') ? bodyB : bodyA;

				if (otherBody.type == 'wall') {
					server.player.health -= server.settings.damage.wall;

					server.updateHealthDisplay();
					server.player.lastHitCounter += server.settings.damage.hitLength;
				} else if (otherBody.type == 'obstacle') {
					server.player.health -= server.settings.damage.obstacle;

					server.updateHealthDisplay();
					server.player.lastHitCounter += server.settings.damage.hitLength;
				} else if (otherBody.type == 'exit') {
					server.win = true;
				}
			} else {
				// Other collisions
			}
		};

		this.b2world.SetContactListener(this.listener);
	};

	this.rotateWorld = function() {
		this.rotIndex++;
		if (this.rotIndex > this.gravities.length - 1) this.rotIndex = 0;
		var grav = this.gravities[this.rotIndex];

		// Change gravity
		server.b2world.SetGravity(new b2Vec2(grav[0], grav[1]));

		// And set the target rotation
		server.targetRotation = this.rotations[this.rotIndex];
	};

	this.updateHealthDisplay = function() {
		var healthWidth = Math.floor((server.player.health / 1000) * $("#status").width());

		$("#status .health").width(healthWidth);
	};
};
<|endoftext|>


<commit_before>var _ = require('underscore'),
  http = require('http'),
  url = require('url'),
  path = require('path'),
  fs = require('fs'),
  WebSocketServer = require('ws').Server;

function Game() {

  _.extend(this, {

    board: [
      {
        type: 'end',
        tries: 0,
        position: {
          x: 0,
          y: 0
        },
      }
    ],

    players: [],
    nextPlayerId: 0,

    sendRPC: function(player, procedure, data) {
      var rpc = {
        procedure: procedure,
        data: data
      };
      player.ws.send(JSON.stringify(rpc));
    },

    broadcastRPC: function(procedure, data) {
      _.each(this.players, _.bind(function(player) {
        this.sendRPC(player, procedure, data);
      }, this));
    },

    onConnection: function(ws) {

      var player = {
        id: this.nextPlayerId++,
        ws: ws,
        moveId: 0,
      };
      this.players.push(player);
      this.sendRPC(player, 'id', player.id);

      while (_.size(this.board) < 10 * _.size(this.players)) {
        this.growBoard();
      }

      _.extend(player, {
        position: { // TODO random spawn position
          x: 0,
          y: 0,
        }
      });
      this.sendRPC(player, 'move', player.position);

      var spawn = this.findBlock(player.position);
      if (spawn !== undefined) {
        spawn.type = 'secured';
        spawn.team = player.id;
      }

      _.each(this.board, _.bind(function(block) {
        this.broadcastRPC('block', block);
      }, this));

      ws.on('message', _.bind(this.onMessage, this, player));
      ws.on('close', _.bind(this.onDisconnection, this, player));

    },

    onDisconnection: function(player) {

      ++player.moveId;

      this.players = _.without(this.players, player);

      if (_.size(this.players) === 0) {
        _.extend(this, new Game());
      }

    },

    onMessage: function(player, rpc) {

      var rpc = JSON.parse(rpc);
      var call = rpc.procedure;
      var data = rpc.data;

      if (call === 'move') {
        this.onMoveMessage(player, data);
      } else {
        console.error('unknown RPC', rpc);
      }

    },

    onMoveMessage: function(player, move) {

      if (
        _.indexOf([-1, 0, 1], move.x) === -1
        || _.indexOf([-1, 0, 1], move.y) === -1
      ) {

        console.error('Cheater detected:', player.ws.upgradeReq.connection.remoteAddress);

      } else {

        if (this.findBlock({
          x: player.position.x + move.x,
          y: player.position.y + move.y,
        }) !== undefined) {

          player.position.x += move.x;
          player.position.y += move.y;

          this.sendRPC(player, 'move', move);
          this.onPlayerMove(player);

        }

      }

    },

    onPlayerMove: function(player) {

      ++player.moveId;
      this.startCapture(player);

    },

    findBlock: function(position) {

      return _.find(
        this.board,
        function(block) {
          return _.isEqual(block.position, position);
        }
      );

    },

    startCapture: function(player) {

      var block = this.findBlock(player.position);

      if (block === undefined) {
        return;
      }

      var moveId = player.moveId;

      function register(self, type, delay, callback) {
        if (block.type === type) {
          _.delay(
            _.bind(
              function() {
                if (block.type === type && player.moveId === moveId) {
                  callback();
                  this.broadcastRPC('block', block);
                  this.startCapture(player);
                }
              },
              self
            ),
            delay
          );
        }
      }

      register(this, 'empty', 3000, function() {
        block.type = 'captured';
        block.team = player.id;
      });

      register(this, 'captured', 7000, function() {
        block.type = 'secured';
        block.team = player.id;
      });

      register(this, 'end', 15000, function() {
        if (Math.random() < block.tries++ / 3) {
          this.broadcastRPC('end');
        } else {
          // TODO
        }
      });

    },

    growBoard: function() {
      var max = (Math.sqrt(_.size(this.board)) + 1) / 2;
      for (var x = -max; x <= max; ++x) {
        this.board.push({
          type: 'empty',
          position: {
            x: x,
            y: max,
          }
        });
        this.board.push({
          type: 'empty',
          position: {
            x: x,
            y: -max,
          }
        });
      }
      for (var y = -max + 1; y < max; ++y) {
        this.board.push({
          type: 'empty',
          position: {
            x: max,
            y: y,
          }
        });
        this.board.push({
          type: 'empty',
          position: {
            x: -max,
            y: y,
          }
        });
      }
    },

  });

};

var webServer = http.createServer(function(request, response) {

  function serve404() {
    response.writeHead(404, { 'Content-type': 'text/plain' });
    response.end('404 Not Found\n');
  }

  var path = url.parse(request.url).pathname.substr(1);

  if (path === '') {
    path = 'index.html';
  } else if (path.indexOf('..') !== -1) {
    serve404();
  }

  var ext = path.split('.');
  ext = ext[ext.length - 1];

  var mime = ({
    html: 'text/html',
    css: 'text/css',
    js: 'application/javascript',
    ogg: 'audio/ogg',
    mp3: 'audio/mpeg',
    wav: 'audio/x-wav',
  })[ext];

  fs.readFile(path, function(err, buf) {
    if (err) {
      serve404();
    } else {
      response.writeHead(200, {
        'Content-type': mime || 'application/octet-stream',
        'Content-length': buf.length,
      });
      response.end(buf);
    }
  });

});

var gameServer = new WebSocketServer({ server: webServer });
var game = new Game();
gameServer.on('connection', function(ws) { game.onConnection(ws); });

webServer.listen(8080);

require('repl').start({}).context.game = game;
<commit_msg>Small refactoring<commit_after>var _ = require('underscore'),
  http = require('http'),
  url = require('url'),
  path = require('path'),
  fs = require('fs'),
  WebSocketServer = require('ws').Server;

function Game() {

  _.extend(this, {

    board: [
      {
        type: 'end',
        tries: 0,
        position: {
          x: 0,
          y: 0
        },
      }
    ],

    players: [],
    nextPlayerId: 0,

    sendRPC: function(player, procedure, data) {
      var rpc = {
        procedure: procedure,
        data: data
      };
      player.ws.send(JSON.stringify(rpc));
    },

    broadcastRPC: function(procedure, data) {
      _.each(this.players, _.bind(function(player) {
        this.sendRPC(player, procedure, data);
      }, this));
    },

    onConnection: function(ws) {

      var player = {
        id: this.nextPlayerId++,
        ws: ws,
        moveId: 0,
      };
      this.players.push(player);
      this.sendRPC(player, 'id', player.id);

      while (_.size(this.board) < 10 * _.size(this.players)) {
        this.growBoard();
      }

      _.extend(player, {
        position: { // TODO random spawn position
          x: 0,
          y: 0,
        }
      });
      this.sendRPC(player, 'move', player.position);

      var spawn = this.findBlock(player.position);
      if (spawn !== undefined) {
        spawn.type = 'secured';
        spawn.team = player.id;
      }

      _.each(this.board, _.bind(function(block) {
        this.broadcastRPC('block', block);
      }, this));

      ws.on('message', _.bind(this.onMessage, this, player));
      ws.on('close', _.bind(this.onDisconnection, this, player));

    },

    onDisconnection: function(player) {

      ++player.moveId;

      this.players = _.without(this.players, player);

      if (_.size(this.players) === 0) {
        _.extend(this, new Game());
      }

    },

    onMessage: function(player, rpc) {

      var rpc = JSON.parse(rpc);
      var call = rpc.procedure;
      var data = rpc.data;

      if (call === 'move') {
        this.onMoveMessage(player, data);
      } else {
        console.error('unknown RPC', rpc);
      }

    },

    onMoveMessage: function(player, move) {

      if (
        _.indexOf([-1, 0, 1], move.x) === -1
        || _.indexOf([-1, 0, 1], move.y) === -1
      ) {

        console.error('Cheater detected:', player.ws.upgradeReq.connection.remoteAddress);

      } else {

        if (this.findBlock({
          x: player.position.x + move.x,
          y: player.position.y + move.y,
        }) !== undefined) {

          player.position.x += move.x;
          player.position.y += move.y;

          this.sendRPC(player, 'move', move);
          this.startCapture(player);

        }

      }

    },

    findBlock: function(position) {

      return _.find(
        this.board,
        function(block) {
          return _.isEqual(block.position, position);
        }
      );

    },

    startCapture: function(player) {

      var block = this.findBlock(player.position);

      if (block === undefined) {
        return;
      }

      var moveId = ++player.moveId;

      function register(self, type, delay, callback) {
        if (block.type === type) {
          _.delay(
            _.bind(
              function() {
                if (block.type === type && player.moveId === moveId) {
                  callback();
                  this.broadcastRPC('block', block);
                  this.startCapture(player);
                }
              },
              self
            ),
            delay
          );
        }
      }

      register(this, 'empty', 3000, function() {
        block.type = 'captured';
        block.team = player.id;
      });

      register(this, 'captured', 7000, function() {
        block.type = 'secured';
        block.team = player.id;
      });

      register(this, 'end', 15000, function() {
        if (Math.random() < block.tries++ / 3) {
          this.broadcastRPC('end');
        } else {
          // TODO
        }
      });

    },

    growBoard: function() {
      var max = (Math.sqrt(_.size(this.board)) + 1) / 2;
      for (var x = -max; x <= max; ++x) {
        this.board.push({
          type: 'empty',
          position: {
            x: x,
            y: max,
          }
        });
        this.board.push({
          type: 'empty',
          position: {
            x: x,
            y: -max,
          }
        });
      }
      for (var y = -max + 1; y < max; ++y) {
        this.board.push({
          type: 'empty',
          position: {
            x: max,
            y: y,
          }
        });
        this.board.push({
          type: 'empty',
          position: {
            x: -max,
            y: y,
          }
        });
      }
    },

  });

};

var webServer = http.createServer(function(request, response) {

  function serve404() {
    response.writeHead(404, { 'Content-type': 'text/plain' });
    response.end('404 Not Found\n');
  }

  var path = url.parse(request.url).pathname.substr(1);

  if (path === '') {
    path = 'index.html';
  } else if (path.indexOf('..') !== -1) {
    serve404();
  }

  var ext = path.split('.');
  ext = ext[ext.length - 1];

  var mime = ({
    html: 'text/html',
    css: 'text/css',
    js: 'application/javascript',
    ogg: 'audio/ogg',
    mp3: 'audio/mpeg',
    wav: 'audio/x-wav',
  })[ext];

  fs.readFile(path, function(err, buf) {
    if (err) {
      serve404();
    } else {
      response.writeHead(200, {
        'Content-type': mime || 'application/octet-stream',
        'Content-length': buf.length,
      });
      response.end(buf);
    }
  });

});

var gameServer = new WebSocketServer({ server: webServer });
var game = new Game();
gameServer.on('connection', function(ws) { game.onConnection(ws); });

webServer.listen(8080);

require('repl').start({}).context.game = game;
<|endoftext|>


<commit_before>
/**
 * Copyright (C) 2012 by Justin Windle
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var Sketch = (function() {

    // ----------------------------------------
    // CONSTANTS
    // ----------------------------------------

    var GUID   = 0;
    var CANVAS = 'canvas';
    var WEB_GL = 'web-gl';

    // ----------------------------------------
    // Members
    // ----------------------------------------

    var ctx;
    var timeout = -1;

    // Default options
    var defaults = {

        fullscreen : true,
        autostart  : true,
        autoclear  : true,
        container  : document.body,
        type       : CANVAS

    };

    // Mixed into the window object
    var globals = {

        PI         : Math.PI,
        TWO_PI     : Math.PI * 2,
        HALF_PI    : Math.PI / 2,
        QUARTER_PI : Math.PI / 4,

        sin        : Math.sin,
        cos        : Math.cos,
        tan        : Math.tan,
        pow        : Math.pow,
        exp        : Math.exp,
        min        : Math.min,
        max        : Math.max,
        sqrt       : Math.sqrt,
        atan       : Math.atan,
        atan2      : Math.atan2,
        ceil       : Math.ceil,
        round      : Math.round,
        floor      : Math.floor,

        // TODO: map, lerp (etc)

        random     : function( min, max ) {

            if ( min && typeof min.length === 'number' && !!min.length )
                return min[ Math.floor( Math.random() * min.length ) ];

            if ( typeof max !== 'number' )
                max = min || 1, min = 0;

            return min + Math.random() * (max - min);
        }
    };

    // Properties & methods mixed into ctx
    var api = {

        millis      : 0,   // Total running milliseconds
        now         : NaN, // Current time in milliseconds
        dt          : NaN, // Delta time between frames (milliseconds)

        keys        : {},  // Hash of currently pressed keys

        mouse       : { x:0, y:0, ox:0, oy:0, dx:0, dy:0 },
        touches     : [],
        initialized : false,
        dragging    : false,
        running     : false,

        // Starts the update / rendering process
        start: function() {

            if ( ctx.running ) return;

            if ( ctx.setup && !ctx.initialized ) ctx.setup();
            ctx.initialized = true;
            ctx.running = true;
            ctx.now = +new Date();
            update();
        },

        // Stops the update / rendering process
        stop: function() {

            cancelAnimationFrame( timeout );
            ctx.running = false;
        },

        // Clears the current drawing context
        // TODO: Empty children here for non-canvas sketches?
        clear: function() {

            if ( ctx.canvas )
                ctx.canvas.width = ctx.canvas.width;
        }
    };

    // ----------------------------------------
    // Helpers
    // ----------------------------------------

    var bind = (function() {

        if ( window.addEventListener )

            return function( el, ev, fn ) { el.addEventListener( ev, fn, false ); };

        else if ( window.attachEvent )

            return function( el, ev, fn ) { el.attachEvent( 'on' + ev, fn ); };

        else el[ 'on' + ev ] = fn;

    })();

    var unbind = (function() {

        if ( window.removeEventListener )

            return function( el, ev, fn ) { el.removeEventListener( ev, fn, false ); };

        else if ( window.detachEvent )

            return function( el, ev, fn ) { el.detachEvent( 'on' + ev, fn ); };

        else el[ 'on' + ev ] = null;

    })();

    // ----------------------------------------
    // Methods
    // ----------------------------------------

    // Sets up & returns a new sketch
    function create( options ) {

        options = extend( options || {}, defaults );

        var id = 'sketch-' + GUID++;
        var canvas = document.createElement( 'canvas' );

        switch ( options.type ) {

            case WEB_GL:

                try { ctx = canvas.getContext( 'webgl', options ); } catch (e) {}
                try { ctx = ctx || canvas.getContext( 'experimental-webgl', options ); } catch (e) {}
                if ( !ctx ) throw 'WebGL not supported';

                break;

            case CANVAS:

                try { ctx = canvas.getContext( '2d', options ); } catch (e) {}
                if ( !ctx ) throw 'Canvas not supported';

                break;

            default:

                canvas = ctx = document.createElement( 'div' );
        }

        // ID & class can be useful
        canvas.className = 'sketch';
        canvas.id = id;

        options.container.appendChild( canvas );

        // Mix globals into the window object
        extend( self, globals );

        // Mix options into ctx
        extend( ctx, options );

        // Add public properties
        extend( ctx, api );

        // Bind event handlers
        bindEvents();

        // Set initial dimensions
        resize();

        if ( ctx.autostart ) setTimeout( ctx.start, 0 );

        return ctx;
    }

    // Soft object merge
    function extend( target, source ) {

        for ( var prop in source ) {

            if ( !target.hasOwnProperty( prop ) ) {
                target[ prop ] = source[ prop ];
            }
        }

        return target;
    }

    // Shallow clones a given object
    function clone( obj ) {

        var copy = {};

        function getCallback( method, context ) {

            return function() {
                method.call( context, arguments );
            };
        }

        for ( var prop in obj ) {

            if ( typeof obj[ prop ] === 'function' )

                copy[ prop ] = getCallback( obj[ prop ], obj );

            else

                copy[ prop ] = obj[ prop ];
        }

        return copy;
    }

    // Sets up sketch mouse & keyboard events
    function bindEvents() {

        var keynames = {
            8:  'BACKSPACE',
            9:  'TAB',
            13: 'ENTER',
            16: 'SHIFT',
            27: 'ESCAPE',
            32: 'SPACE',
            37: 'LEFT',
            38: 'UP',
            39: 'RIGHT',
            40: 'DOWN'
        };

        // Explicitly set all keys to false initially
        for ( var name in keynames ) {
            api.keys[ keynames[ name ] ] = false;
        }

        // maps a key code to a key name
        function map( code ) {
            return keynames[ code ] || String.fromCharCode( code );
        }

        // Update mouse position
        function updateMouse( coord ) {

            ctx.mouse.ox = ctx.mouse.x;
            ctx.mouse.oy = ctx.mouse.y;

            ctx.mouse.x = coord.x;
            ctx.mouse.y = coord.y;

            ctx.mouse.dx = ctx.mouse.x - ctx.mouse.ox;
            ctx.mouse.dy = ctx.mouse.y - ctx.mouse.oy;
        }

        var old = {};

        // Augments the native mouse event
        function augment( event ) {

            var o, e = clone( event );
            e.original = event;

            // Compute container offset
            for ( var el = ctx.canvas, ox = 0, oy = 0; el; el = el.offsetParent ) {

                ox += el.offsetLeft;
                oy += el.offsetTop;
            }

            // Normalise touches / mouse
            if ( e.touches && !!e.touches.length ) {

                for ( var i = e.touches.length - 1, touch; i >= 0; i-- ) {

                    touch = e.touches[i];
                    touch.x = touch.pageX - ox;
                    touch.y = touch.pageY - oy;

                    o = old[i] || touch;

                    touch.dx = touch.x - o.x;
                    touch.dy = touch.y - o.x;

                    touch.ox = o.x;
                    touch.oy = o.y;

                    old[i] = clone( touch );
                }

            } else {

                e.x = e.pageX - ox;
                e.y = e.pageY - oy;

                o = old[ 'mouse' ] || e;

                e.dx = e.x - o.x;
                e.dy = e.y - o.y;

                e.ox = o.x;
                e.oy = o.y;

                old[ 'mouse' ] = e;
            }

            return e;
        }

        // Touch events

        function touchstart( event ) {

            // Prevent scrolling (should this be optional?)
            event.preventDefault();

            event = augment( event );
            ctx.touches = event.touches;
            updateMouse( ctx.touches[0] );

            if ( ctx.touchstart ) ctx.touchstart( event );
        }

        function touchmove( event ) {

            event = augment( event );
            ctx.touches = event.touches;
            updateMouse( ctx.touches[0] );

            if ( ctx.touchmove ) ctx.touchmove( event );
            if ( ctx.mousemove ) ctx.mousemove( event );
        }

        function touchend( event ) {

            event = augment( event );

            // Cleanup ended touches
            if ( !event.touches.length ) old = {};
            else for ( var id in old ) if ( !event.touches[ id ] ) delete old[ id ];

            if ( ctx.touchend ) ctx.touchend( event );
        }

        // Mouse events

        function mouseover( event ) {

            event = augment( event );
            if ( ctx.mouseover ) ctx.mouseover( event );
        }

        function mousedown( event ) {

            event = augment( event );

            if ( !ctx.dragging ) {

                unbind( ctx.canvas, 'mousemove', mousemove );
                unbind( ctx.canvas, 'mouseup', mouseup );

                bind( document, 'mousemove', mousemove );
                bind( document, 'mouseup', mouseup );

                ctx.dragging = true;
            }

            ctx.touches = [ event ];

            if ( ctx.mousedown ) ctx.mousedown( event );
        }

        function mousemove( event ) {

            event = augment( event );
            updateMouse( event );

            ctx.touches = [ event ];

            if ( ctx.mousemove ) ctx.mousemove( event );
            if ( ctx.touchmove ) ctx.touchmove( event );
        }

        function mouseout( event ) {

            event = augment( event );
            if ( ctx.mouseout ) ctx.mouseout( event );
        }

        function mouseup( event ) {

            event = augment( event );

            if ( ctx.dragging ) {

                unbind( document, 'mousemove', mousemove );
                unbind( document, 'mouseup', mouseup );

                bind( ctx.canvas, 'mousemove', mousemove );
                bind( ctx.canvas, 'mouseup', mouseup );

                ctx.dragging = false;
            }

            delete old[ 'mouse' ];

            if ( ctx.mouseup ) ctx.mouseup( event );
        }

        function click( event ) {

            event = augment( event );
            if ( ctx.click ) ctx.click( event );
        }

        // Keyboard events

        function keydown( event ) {

            ctx.keys[ map( event.keyCode ) ] = true;
            ctx.keys[ event.keyCode ] = true;

            if ( ctx.keydown ) ctx.keydown( event );
        }

        function keyup( event ) {

            ctx.keys[ map( event.keyCode ) ] = false;
            ctx.keys[ event.keyCode ] = false;

            if ( ctx.keyup ) ctx.keyup( event );
        }

        // Bind to context

        bind( ctx.canvas, 'touchstart', touchstart );
        bind( ctx.canvas, 'touchmove', touchmove );
        bind( ctx.canvas, 'touchend', touchend );

        bind( ctx.canvas, 'mouseover', mouseover );
        bind( ctx.canvas, 'mousedown', mousedown );
        bind( ctx.canvas, 'mousemove', mousemove );
        bind( ctx.canvas, 'mouseout', mouseout );
        bind( ctx.canvas, 'mouseup', mouseup );
        bind( ctx.canvas, 'click', click );

        bind( document, 'keydown', keydown );
        bind( document, 'keyup', keyup );

        bind( window, 'resize', resize );
    }

    // ----------------------------------------
    // Event handlers
    // ----------------------------------------

    function update( now ) {

        ctx.dt = ( now = now || +new Date() ) - ctx.now;
        ctx.millis += ctx.dt;
        ctx.now = now;

        if ( ctx.update ) ctx.update( ctx.dt );
        if ( ctx.autoclear ) ctx.clear();
        if ( ctx.draw ) ctx.draw( ctx );

        timeout = requestAnimationFrame( update );
    }

    function resize( event ) {

        if ( ctx.fullscreen ) {

            ctx.height = ctx.canvas.height = window.innerHeight;
            ctx.width = ctx.canvas.width = window.innerWidth;

        } else {

            ctx.canvas.height = ctx.height;
            ctx.canvas.width = ctx.width;
        }

        if ( ctx.resize ) ctx.resize();
    }

    // ----------------------------------------
    // Public API
    // ----------------------------------------

    return {

        CANVAS: CANVAS,
        WEB_GL: WEB_GL,

        create: create
    };

})();

/**
 * requestAnimationFrame polyfill by Erik Möller
 * Fixes from Paul Irish and Tino Zijdel
 *
 * @see http://goo.gl/ZC1Lm
 * @seehttp://goo.gl/X0h6k
 */

(function(){for(var d=0,a=["ms","moz","webkit","o"],b=0;b<a.length&&!window.requestAnimationFrame;++b)window.requestAnimationFrame=window[a[b]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[a[b]+"CancelAnimationFrame"]||window[a[b]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(b){var a=(new Date).getTime(),c=Math.max(0,16-(a-d)),e=window.setTimeout(function(){b(a+c)},c);d=a+c;return e});window.cancelAnimationFrame||(window.cancelAnimationFrame=function(a){clearTimeout(a)})})();<commit_msg>Use Date.now and provide polyfill. #6<commit_after>
/**
 * Copyright (C) 2012 by Justin Windle
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

var Sketch = (function() {

    // ----------------------------------------
    // CONSTANTS
    // ----------------------------------------

    var GUID   = 0;
    var CANVAS = 'canvas';
    var WEB_GL = 'web-gl';

    // ----------------------------------------
    // Members
    // ----------------------------------------

    var ctx;
    var timeout = -1;

    // Default options
    var defaults = {

        fullscreen : true,
        autostart  : true,
        autoclear  : true,
        container  : document.body,
        type       : CANVAS

    };

    // Mixed into the window object
    var globals = {

        PI         : Math.PI,
        TWO_PI     : Math.PI * 2,
        HALF_PI    : Math.PI / 2,
        QUARTER_PI : Math.PI / 4,

        sin        : Math.sin,
        cos        : Math.cos,
        tan        : Math.tan,
        pow        : Math.pow,
        exp        : Math.exp,
        min        : Math.min,
        max        : Math.max,
        sqrt       : Math.sqrt,
        atan       : Math.atan,
        atan2      : Math.atan2,
        ceil       : Math.ceil,
        round      : Math.round,
        floor      : Math.floor,

        // TODO: map, lerp (etc)

        random     : function( min, max ) {

            if ( min && typeof min.length === 'number' && !!min.length )
                return min[ Math.floor( Math.random() * min.length ) ];

            if ( typeof max !== 'number' )
                max = min || 1, min = 0;

            return min + Math.random() * (max - min);
        }
    };

    // Properties & methods mixed into ctx
    var api = {

        millis      : 0,   // Total running milliseconds
        now         : NaN, // Current time in milliseconds
        dt          : NaN, // Delta time between frames (milliseconds)

        keys        : {},  // Hash of currently pressed keys

        mouse       : { x:0, y:0, ox:0, oy:0, dx:0, dy:0 },
        touches     : [],
        initialized : false,
        dragging    : false,
        running     : false,

        // Starts the update / rendering process
        start: function() {

            if ( ctx.running ) return;

            if ( ctx.setup && !ctx.initialized ) ctx.setup();
            ctx.initialized = true;
            ctx.running = true;
            ctx.now = Date.now();
            update();
        },

        // Stops the update / rendering process
        stop: function() {

            cancelAnimationFrame( timeout );
            ctx.running = false;
        },

        // Clears the current drawing context
        // TODO: Empty children here for non-canvas sketches?
        clear: function() {

            if ( ctx.canvas )
                ctx.canvas.width = ctx.canvas.width;
        }
    };

    // ----------------------------------------
    // Helpers
    // ----------------------------------------

    var bind = (function() {

        if ( window.addEventListener )

            return function( el, ev, fn ) { el.addEventListener( ev, fn, false ); };

        else if ( window.attachEvent )

            return function( el, ev, fn ) { el.attachEvent( 'on' + ev, fn ); };

        else el[ 'on' + ev ] = fn;

    })();

    var unbind = (function() {

        if ( window.removeEventListener )

            return function( el, ev, fn ) { el.removeEventListener( ev, fn, false ); };

        else if ( window.detachEvent )

            return function( el, ev, fn ) { el.detachEvent( 'on' + ev, fn ); };

        else el[ 'on' + ev ] = null;

    })();

    // ----------------------------------------
    // Methods
    // ----------------------------------------

    // Sets up & returns a new sketch
    function create( options ) {

        options = extend( options || {}, defaults );

        var id = 'sketch-' + GUID++;
        var canvas = document.createElement( 'canvas' );

        switch ( options.type ) {

            case WEB_GL:

                try { ctx = canvas.getContext( 'webgl', options ); } catch (e) {}
                try { ctx = ctx || canvas.getContext( 'experimental-webgl', options ); } catch (e) {}
                if ( !ctx ) throw 'WebGL not supported';

                break;

            case CANVAS:

                try { ctx = canvas.getContext( '2d', options ); } catch (e) {}
                if ( !ctx ) throw 'Canvas not supported';

                break;

            default:

                canvas = ctx = document.createElement( 'div' );
        }

        // ID & class can be useful
        canvas.className = 'sketch';
        canvas.id = id;

        options.container.appendChild( canvas );

        // Mix globals into the window object
        extend( self, globals );

        // Mix options into ctx
        extend( ctx, options );

        // Add public properties
        extend( ctx, api );

        // Bind event handlers
        bindEvents();

        // Set initial dimensions
        resize();

        if ( ctx.autostart ) setTimeout( ctx.start, 0 );

        return ctx;
    }

    // Soft object merge
    function extend( target, source ) {

        for ( var prop in source ) {

            if ( !target.hasOwnProperty( prop ) ) {
                target[ prop ] = source[ prop ];
            }
        }

        return target;
    }

    // Shallow clones a given object
    function clone( obj ) {

        var copy = {};

        function getCallback( method, context ) {

            return function() {
                method.call( context, arguments );
            };
        }

        for ( var prop in obj ) {

            if ( typeof obj[ prop ] === 'function' )

                copy[ prop ] = getCallback( obj[ prop ], obj );

            else

                copy[ prop ] = obj[ prop ];
        }

        return copy;
    }

    // Sets up sketch mouse & keyboard events
    function bindEvents() {

        var keynames = {
            8:  'BACKSPACE',
            9:  'TAB',
            13: 'ENTER',
            16: 'SHIFT',
            27: 'ESCAPE',
            32: 'SPACE',
            37: 'LEFT',
            38: 'UP',
            39: 'RIGHT',
            40: 'DOWN'
        };

        // Explicitly set all keys to false initially
        for ( var name in keynames ) {
            api.keys[ keynames[ name ] ] = false;
        }

        // maps a key code to a key name
        function map( code ) {
            return keynames[ code ] || String.fromCharCode( code );
        }

        // Update mouse position
        function updateMouse( coord ) {

            ctx.mouse.ox = ctx.mouse.x;
            ctx.mouse.oy = ctx.mouse.y;

            ctx.mouse.x = coord.x;
            ctx.mouse.y = coord.y;

            ctx.mouse.dx = ctx.mouse.x - ctx.mouse.ox;
            ctx.mouse.dy = ctx.mouse.y - ctx.mouse.oy;
        }

        var old = {};

        // Augments the native mouse event
        function augment( event ) {

            var o, e = clone( event );
            e.original = event;

            // Compute container offset
            for ( var el = ctx.canvas, ox = 0, oy = 0; el; el = el.offsetParent ) {

                ox += el.offsetLeft;
                oy += el.offsetTop;
            }

            // Normalise touches / mouse
            if ( e.touches && !!e.touches.length ) {

                for ( var i = e.touches.length - 1, touch; i >= 0; i-- ) {

                    touch = e.touches[i];
                    touch.x = touch.pageX - ox;
                    touch.y = touch.pageY - oy;

                    o = old[i] || touch;

                    touch.dx = touch.x - o.x;
                    touch.dy = touch.y - o.x;

                    touch.ox = o.x;
                    touch.oy = o.y;

                    old[i] = clone( touch );
                }

            } else {

                e.x = e.pageX - ox;
                e.y = e.pageY - oy;

                o = old[ 'mouse' ] || e;

                e.dx = e.x - o.x;
                e.dy = e.y - o.y;

                e.ox = o.x;
                e.oy = o.y;

                old[ 'mouse' ] = e;
            }

            return e;
        }

        // Touch events

        function touchstart( event ) {

            // Prevent scrolling (should this be optional?)
            event.preventDefault();

            event = augment( event );
            ctx.touches = event.touches;
            updateMouse( ctx.touches[0] );

            if ( ctx.touchstart ) ctx.touchstart( event );
        }

        function touchmove( event ) {

            event = augment( event );
            ctx.touches = event.touches;
            updateMouse( ctx.touches[0] );

            if ( ctx.touchmove ) ctx.touchmove( event );
            if ( ctx.mousemove ) ctx.mousemove( event );
        }

        function touchend( event ) {

            event = augment( event );

            // Cleanup ended touches
            if ( !event.touches.length ) old = {};
            else for ( var id in old ) if ( !event.touches[ id ] ) delete old[ id ];

            if ( ctx.touchend ) ctx.touchend( event );
        }

        // Mouse events

        function mouseover( event ) {

            event = augment( event );
            if ( ctx.mouseover ) ctx.mouseover( event );
        }

        function mousedown( event ) {

            event = augment( event );

            if ( !ctx.dragging ) {

                unbind( ctx.canvas, 'mousemove', mousemove );
                unbind( ctx.canvas, 'mouseup', mouseup );

                bind( document, 'mousemove', mousemove );
                bind( document, 'mouseup', mouseup );

                ctx.dragging = true;
            }

            ctx.touches = [ event ];

            if ( ctx.mousedown ) ctx.mousedown( event );
        }

        function mousemove( event ) {

            event = augment( event );
            updateMouse( event );

            ctx.touches = [ event ];

            if ( ctx.mousemove ) ctx.mousemove( event );
            if ( ctx.touchmove ) ctx.touchmove( event );
        }

        function mouseout( event ) {

            event = augment( event );
            if ( ctx.mouseout ) ctx.mouseout( event );
        }

        function mouseup( event ) {

            event = augment( event );

            if ( ctx.dragging ) {

                unbind( document, 'mousemove', mousemove );
                unbind( document, 'mouseup', mouseup );

                bind( ctx.canvas, 'mousemove', mousemove );
                bind( ctx.canvas, 'mouseup', mouseup );

                ctx.dragging = false;
            }

            delete old[ 'mouse' ];

            if ( ctx.mouseup ) ctx.mouseup( event );
        }

        function click( event ) {

            event = augment( event );
            if ( ctx.click ) ctx.click( event );
        }

        // Keyboard events

        function keydown( event ) {

            ctx.keys[ map( event.keyCode ) ] = true;
            ctx.keys[ event.keyCode ] = true;

            if ( ctx.keydown ) ctx.keydown( event );
        }

        function keyup( event ) {

            ctx.keys[ map( event.keyCode ) ] = false;
            ctx.keys[ event.keyCode ] = false;

            if ( ctx.keyup ) ctx.keyup( event );
        }

        // Bind to context

        bind( ctx.canvas, 'touchstart', touchstart );
        bind( ctx.canvas, 'touchmove', touchmove );
        bind( ctx.canvas, 'touchend', touchend );

        bind( ctx.canvas, 'mouseover', mouseover );
        bind( ctx.canvas, 'mousedown', mousedown );
        bind( ctx.canvas, 'mousemove', mousemove );
        bind( ctx.canvas, 'mouseout', mouseout );
        bind( ctx.canvas, 'mouseup', mouseup );
        bind( ctx.canvas, 'click', click );

        bind( document, 'keydown', keydown );
        bind( document, 'keyup', keyup );

        bind( window, 'resize', resize );
    }

    // ----------------------------------------
    // Event handlers
    // ----------------------------------------

    function update( now ) {

        ctx.dt = ( now = now || +new Date() ) - ctx.now;
        ctx.millis += ctx.dt;
        ctx.now = now;

        if ( ctx.update ) ctx.update( ctx.dt );
        if ( ctx.autoclear ) ctx.clear();
        if ( ctx.draw ) ctx.draw( ctx );

        timeout = requestAnimationFrame( update );
    }

    function resize( event ) {

        if ( ctx.fullscreen ) {

            ctx.height = ctx.canvas.height = window.innerHeight;
            ctx.width = ctx.canvas.width = window.innerWidth;

        } else {

            ctx.canvas.height = ctx.height;
            ctx.canvas.width = ctx.width;
        }

        if ( ctx.resize ) ctx.resize();
    }

    // ----------------------------------------
    // Public API
    // ----------------------------------------

    return {

        CANVAS: CANVAS,
        WEB_GL: WEB_GL,

        create: create
    };

})();

// ----------------------------------------
// Useful polyfills
// ----------------------------------------

if ( !Date.now ) {
    Date.now = function now() {
        return +( new Date() );
    };
}

/**
 * requestAnimationFrame polyfill by Erik Möller
 * Fixes from Paul Irish and Tino Zijdel
 *
 * @see http://goo.gl/ZC1Lm
 * @seehttp://goo.gl/X0h6k
 */

(function(){for(var d=0,a=["ms","moz","webkit","o"],b=0;b<a.length&&!window.requestAnimationFrame;++b)window.requestAnimationFrame=window[a[b]+"RequestAnimationFrame"],window.cancelAnimationFrame=window[a[b]+"CancelAnimationFrame"]||window[a[b]+"CancelRequestAnimationFrame"];window.requestAnimationFrame||(window.requestAnimationFrame=function(b){var a=Date.now(),c=Math.max(0,16-(a-d)),e=window.setTimeout(function(){b(a+c)},c);d=a+c;return e});window.cancelAnimationFrame||(window.cancelAnimationFrame=function(a){clearTimeout(a)})})();<|endoftext|>


<commit_before>    function wikiSlider(options){

		var url = 'https://en.wikipedia.org/w/api.php?callback=?';
	    var postDict ={
		        rvdir:'older',
		        format:'json',
		        action:'query',
		        prop:'revisions',
				titles:'',
		        rvprop:'user|timestamp|flags|ids|size',
		        rvlimit:'max',
	    	};

		this.height = options.height;
        this.barGraphBarwidth = options.barGraphBarwidth;
        this.enlargedBarGraphBarwidth = options.enlargedBarGraphBarwidth;

		/* Callbacks for the Primary & Secondry slider*/
		this.primarySliderCallback = null ;
		this.secondrySliderCallback = null;
		if (options.primarySliderMoveCallback && typeof(options.primarySliderMoveCallback) === 'function'){
			this.primarySliderCallback = options.primarySliderMoveCallback;
		}

		if (options.secondrySliderMoveCallback && typeof(options.secondrySliderMoveCallback) === 'function'){
			this.secondrySliderCallback = options.secondrySliderMoveCallback;
		}

		/* Used Variables */
		var svgWidth  = 0;
		var hoverUser = '';
		var completeRevData = [], secondrySliderSelection = [];
		var rvContinueHash = true,gettingDataFlag = true;
		var yscale = null, yscale2 = null, xscale = null;
		var brush = null;
		var toolTipDiv, svg, svgBox, svgEnlargedBox;
		var primaryContainer, primaryGraph, secondaryContainer, secondaryGraph, newGraph;
		var outerLength = 50, enlargedLength = 80;
		var endLine, startDate, endDate;
		var bars;
		var timeFormat = "%Y-%m-%dT%H:%M:%SZ";
        var timeParse = d3.time.format(timeFormat);
		var that = this;
		this.init = function(){
			/*Enlarged view toggle*/
        	d3.select('.enlargedButton').on('click',function(){
	            if (d3.select('#enlarged').style('height').split('px')[0] > 20){
	                d3.select('#enlarged').style({'height':'15px','top':'145px'}).select('svg').style('display','none');
	                d3.select(this).text('^');
	            }
	            else{
	                d3.select('#enlarged').style({'height':enlargedLength*2+'px', top:0+'px'}).select('svg').style('display','block');
	                d3.select(this).text('v');
	            }
	            });

        	/* tooltip div */
        	tooltipDiv = d3.select("body").append("div").attr("class", "tooltip").style("opacity", 0);

        	svg = d3.select('#outer').append('svg').attr({'height':100,'width':600});
            svgBox = svg.append('g').attr({'transform':'translate(0,0)'});
            svgEnlargedBox = d3.select('#enlarged').append('svg').attr({'height':enlargedLength*2,'width':450})
                                            .append('g').attr('transform','translate(0,0)');

	        primaryContainer = svgBox.append('g').attr('id','primaryContainer');
	        primaryGraph = primaryContainer.append('g').attr('id','primaryGraph');
	        primaryContainer.append('text').text('Edit Size').attr({'x':10,'y':10,'class':'legendText'});
	        secondaryContainer = svgEnlargedBox.append('g').attr({'id':'secondaryContainer'});
	        secondaryGraph = secondaryContainer.append('g').attr('id','secondaryGraph');

        	/*Baseline in enlarged graph*/
        	d3.select('#enlarged svg').append('line').attr({'x1':0,'x2':450,'y1':enlargedLength,'y2':enlargedLength,'stroke':'gray','stroke-width':'.5'});
			d3.select('#enlarged svg').append('line').attr({'x1':0,'x2':0,'y1':enlargedLength,'y2':enlargedLength*2-10,'stroke':'gray','stroke-width':'.5'});
			endLine = d3.select('#enlarged svg').append('line').attr({'x1':450,'x2':450,'y1':enlargedLength,'y2':enlargedLength*2-10,'stroke':'gray','stroke-width':'.5'});
        	startDate = secondaryContainer.append('text').attr({'x':0,'y':enlargedLength*2-10}).style('font-size',9);
			endDate = secondaryContainer.append('text').attr({'x':450,'y':enlargedLength*2-10}).style('font-size',9);

			/* Calling it direcly with getData by the user*/
        	//this.getData();

		};
		this.getData = function(title){
			if(title){
				postDict['titles'] = title;
			}
			if (gettingDataFlag && rvContinueHash){
				//console.log('postDict',postDict);
        		$.getJSON(url,postDict,function(data){
            		if ('query-continue' in data){
                    	rvContinueHash = data['query-continue'].revisions.rvcontinue;
                    	postDict['rvcontinue'] = rvContinueHash;
                	}
            		else{
                    	rvContinueHash = null;
            		}
	            console.log(rvContinueHash);
	            var resultKey = Object.keys(data.query.pages);
	            var revData = data.query.pages[resultKey].revisions;
	            completeRevData = completeRevData.concat(revData);
	            parsedData = that.parseData(completeRevData);
	            that.fixScales();
	            that.addData(primaryGraph,completeRevData,yscale);
	            that.callBrush();
	            gettingDataFlag = true;
        		});
        		gettingDataFlag = false;
        	}
        	else{
            	return;
        	}
		};

		this.cleanUp = function () {
			  completeRevData = [];
			  rvContinueHash = true;
			  postDict ={
		        rvdir:'older',
		        format:'json',
		        action:'query',
		        prop:'revisions',
				titles:'',
		        rvprop:'user|timestamp|flags|ids|size',
		        rvlimit:'max',
	    	};
		};
		//Cleanup big time
		/** To highlight the revision currently being animated **/
		this.modifySecondryGraph = function(field,value,color) {
			newGraph.filter(function(){return true;})
				.style({'fill': function(d,i){
							return d.dir == 'p' ? 'blue':'red' ;}
					});
			newGraph.filter(function(d){
				return d[field] == value;
			}).style('fill',color);
		};
		//Cleanup big time
		/** To get the list of revisions selected in the slider **/
		this.getSelection = function () {
			var brushExtent = brush.extent();
			var start = Math.floor(brushExtent[0]/that.barGraphBarwidth);
			var end = Math.ceil(brushExtent[1]/that.barGraphBarwidth);
			return completeRevData.slice(start,end);
		};
		this.getSecondrySliderSelection = function (i){
			return that.getSelection().slice(0,i+1).reverse();
		};
		this.wikiNameSpace = function (language) {
	  		usingLanguageNamespace = language;
	  		baseUrl = 'https://'+language+'.wikipedia.org/w/api.php?callback=?';
		};

		this.parseData = function(data){
	        data.forEach(function(d,i,array){
	                if(array[i+1]){
	                    d.editSize = d.size - array[i+1].size;
	                    if (i == 0 ){
	                        d.timeDiff = 0;
	                    }
	                    else{
	                        d.timeDiff = Math.floor((timeParse.parse(array[i-1].timestamp) - timeParse.parse(d.timestamp) ) /86400000);
	                    }

	                    if (d.editSize >= 0){
	                        d.dir = 'p';
	                    }
	                    else{
	                       d.dir = 'n';
	                       d.editSize = d.editSize * -1;
	                    }
	                }
	                else{
	                    d.editSize = null;
	                    d.timeDiff = null;
	                }
	                //console.log(d.editSize);
	            if (d.parentid == 0){
	                d.editSize = d.size;
	                d.timeDiff = 0;
	                d.dir = 'p';
	            }
	            if ('minor' in d){
	                d.minor = true;
	            }
	            else{
	                d.minor = false;
	            }
	            d.date = timeParse.parse(d.timestamp);
	            });

        		data.svgWidth = data.length*that.barGraphBarwidth < 100 ? 100 : data.length*that.barGraphBarwidth;
        		data.yscale = [0,d3.max(data, function(d) { return d.editSize; })];

   		};

   		this.fixScales = function (){
	        if(xscale == null){
				yscale = d3.scale.pow().exponent(.4).domain([0,d3.max(completeRevData, function(d) { return d.editSize; })]).range([2,outerLength]);
				yscale2 = d3.scale.pow().exponent(.4).domain([0,d3.max(completeRevData, function(d) { return d.editSize; })]).range([2,enlargedLength]);
				xscale = d3.scale.linear().domain([0,completeRevData.svgWidth]).range([0,completeRevData.svgWidth]);
				diffscale = d3.scale.linear().domain([0,d3.max(completeRevData, function(d) { return d.timeDiff; })]).range([0,10]);
			}
	        else{
				yscale.domain([0,d3.max(completeRevData, function(d) { return d.editSize; })]);
				yscale2.domain([0,d3.max(completeRevData, function(d) { return d.editSize; })]);
				xscale.domain([0,completeRevData.svgWidth]).range([0,completeRevData.svgWidth]);
				diffscale.domain([0,d3.max(completeRevData, function(d) { return d.timeDiff; })]);
	        }
	        svg.attr('width',completeRevData.svgWidth);
    	};

    	this.addData = function (rect,data,yscale){
	        bars = rect.selectAll('rect').data(data);
	        bars.enter().append("rect");
	        bars.attr({
                        'x':function(d,i){ return i * that.barGraphBarwidth; },
                        'y':function(d,i){ return d.dir == 'p' ? outerLength - yscale(d.editSize) : outerLength; },
                        'width':that.barGraphBarwidth,
                        'height':function(d,i){ return yscale(d.editSize); },
                        'class':function(d,i){ return d.dir == 'p' ? 'blue':'red'; },
                        'timestamp':function(d){ return d.timestamp; }
                        });
	        bars.exit().remove();
    	};

    	this.callBrush = function (){
	        if (brush != null){
		        brush.x(xscale);
		        d3.select('#primaryBrush').call(brush);
			}
			else{
		            brush = d3.svg.brush().x(xscale).extent([10, 50]).on("brush", brushmove);
		            var brushg = svgBox.append("g").attr("class", "brush")
		                                    .attr("id","primaryBrush")
		                                    .call(brush);
		            brushg.selectAll("rect").attr("height", 100)
		                                    .attr("y",0);
		            //Fix it
		            //brushmove();
		            temp();
			}
		};
		this.handleScroll = function (){
        	//d3.select('#outer').transition().property('scrollLeft',brush.extent()[0]);
         	$('#outer').scrollLeft(brush.extent()[0]);
   		};
   		this.fixWidth = function (width){
			d3.select('#enlarged').style('width',width+'px').select('svg').style('width',width).select('line').attr('x2',width);
			endLine.attr({'x1':width,'x2':width});
    	};
		function brushmove(){
	        var brushExtent = brush.extent();
	        slid_s = d3.event.target.extent();
	        if(slid_s[1] - slid_s[0] < 100){

		        /* the secondary slider */
		        temp();

		        if (brushExtent[1]> completeRevData.length*that.barGraphBarwidth - 50){
		            that.getData();
		        }
	        }
	        else{
	            d3.event.target.extent([slid_s[0],slid_s[0]+95]); d3.event.target(d3.select(this));
	        }
	        /*Calling Primary Slider callback*/
			if (that.primarySliderCallback){
				that.primarySliderCallback();
			}
    	}
    	function temp(){
    			var brushExtent = brush.extent();
	    		 	var new_graph = completeRevData.slice(brushExtent[0]/that.barGraphBarwidth,brushExtent[1]/that.barGraphBarwidth );
	    		 	if (new_graph.length){
				        var diffScaleAbs = 0;
						var lastX = 0;
				        //console.log(brushExtent);
				        newGraph = secondaryGraph.selectAll("rect").data(new_graph);
				        newGraph.enter().append("rect");
				        newGraph.attr("x",function(d,i){
				            diffScaleAbs += d.timeDiff*3;
					    	lastX = diffScaleAbs +  i*that.enlargedBarGraphBarwidth;
				            return lastX;
						})
				        .attr("y",function(d,i){
							return d.dir == 'p' ? enlargedLength - yscale2(d.editSize) : enlargedLength;})
						.attr("width",that.enlargedBarGraphBarwidth)
						.attr("height",function(d){ return yscale2(d.editSize); })
						.attr("class",function(d){ return d.dir=='p'?'pointer blue':'pointer red'; })
						.attr("timeDiff",function(d){ return d.timeDiff; })
						.attr("title",function(d){ return d.user; })
						.attr("number",function(d,i){ return i; })
						.on("mouseover", function(d) {
							tooltipDiv.transition().duration(200).style("opacity", .9);
							bars.filter(function(d){ return d.user == hoverUser; })
							.style({'fill':'steelblue'});
						    hoverUser = d.user;
				            tooltipDiv.html(d.user + "<br/>"  + d.date)
				                .style("left", (d3.event.pageX) + "px")
				                .style("top", (d3.event.pageY - 28) + "px");
				            bars.filter(function(d){
								return d.user == hoverUser;
								}).style({'fill':'orange'});
						})
				        .on("mouseout", function(d) {
				            tooltipDiv.transition()
				                .duration(500)
				                .style("opacity", 0);
				        })
				        .on("click", function(d,i){
							if (that.secondrySliderCallback){
								var selectedEdits = that.getSecondrySliderSelection(i);
								that.secondrySliderCallback(selectedEdits,1);
							}
				        })
				       .style({'fill': function(d,i){
							return d.dir == 'p' ? 'blue':'red' ;}
						});

						newGraph.exit().remove();
						startDate.text(timeParse.parse(new_graph[0].timestamp).toDateString().slice(4));
						endDate.text(timeParse.parse(new_graph[new_graph.length-1].timestamp).toDateString().slice(4)).attr({'x':lastX-50});
						that.fixWidth(lastX);
				}
    	}
    	return this;
    }<commit_msg>Fixing getSelection to handle when nothing has been clicked<commit_after>    function wikiSlider(options){

		var url = 'https://en.wikipedia.org/w/api.php?callback=?';
	    var postDict ={
		        rvdir:'older',
		        format:'json',
		        action:'query',
		        prop:'revisions',
				titles:'',
		        rvprop:'user|timestamp|flags|ids|size',
		        rvlimit:'max',
	    	};

		this.height = options.height;
        this.barGraphBarwidth = options.barGraphBarwidth;
        this.enlargedBarGraphBarwidth = options.enlargedBarGraphBarwidth;

		/* Callbacks for the Primary & Secondry slider*/
		this.primarySliderCallback = null ;
		this.secondrySliderCallback = null;
		if (options.primarySliderMoveCallback && typeof(options.primarySliderMoveCallback) === 'function'){
			this.primarySliderCallback = options.primarySliderMoveCallback;
		}

		if (options.secondrySliderMoveCallback && typeof(options.secondrySliderMoveCallback) === 'function'){
			this.secondrySliderCallback = options.secondrySliderMoveCallback;
		}

		/* Used Variables */
		var svgWidth  = 0;
		var hoverUser = '';
		var completeRevData = [], secondrySliderSelection = [];
		var rvContinueHash = true,gettingDataFlag = true;
		var yscale = null, yscale2 = null, xscale = null;
		var brush = null;
		var toolTipDiv, svg, svgBox, svgEnlargedBox;
		var primaryContainer, primaryGraph, secondaryContainer, secondaryGraph, newGraph;
		var outerLength = 50, enlargedLength = 80;
		var endLine, startDate, endDate;
		var bars;
		var timeFormat = "%Y-%m-%dT%H:%M:%SZ";
        var timeParse = d3.time.format(timeFormat);
		var that = this;
		this.init = function(){
			/*Enlarged view toggle*/
        	d3.select('.enlargedButton').on('click',function(){
	            if (d3.select('#enlarged').style('height').split('px')[0] > 20){
	                d3.select('#enlarged').style({'height':'15px','top':'145px'}).select('svg').style('display','none');
	                d3.select(this).text('^');
	            }
	            else{
	                d3.select('#enlarged').style({'height':enlargedLength*2+'px', top:0+'px'}).select('svg').style('display','block');
	                d3.select(this).text('v');
	            }
	            });

        	/* tooltip div */
        	tooltipDiv = d3.select("body").append("div").attr("class", "tooltip").style("opacity", 0);

        	svg = d3.select('#outer').append('svg').attr({'height':100,'width':600});
            svgBox = svg.append('g').attr({'transform':'translate(0,0)'});
            svgEnlargedBox = d3.select('#enlarged').append('svg').attr({'height':enlargedLength*2,'width':450})
                                            .append('g').attr('transform','translate(0,0)');

	        primaryContainer = svgBox.append('g').attr('id','primaryContainer');
	        primaryGraph = primaryContainer.append('g').attr('id','primaryGraph');
	        primaryContainer.append('text').text('Edit Size').attr({'x':10,'y':10,'class':'legendText'});
	        secondaryContainer = svgEnlargedBox.append('g').attr({'id':'secondaryContainer'});
	        secondaryGraph = secondaryContainer.append('g').attr('id','secondaryGraph');

        	/*Baseline in enlarged graph*/
        	d3.select('#enlarged svg').append('line').attr({'x1':0,'x2':450,'y1':enlargedLength,'y2':enlargedLength,'stroke':'gray','stroke-width':'.5'});
			d3.select('#enlarged svg').append('line').attr({'x1':0,'x2':0,'y1':enlargedLength,'y2':enlargedLength*2-10,'stroke':'gray','stroke-width':'.5'});
			endLine = d3.select('#enlarged svg').append('line').attr({'x1':450,'x2':450,'y1':enlargedLength,'y2':enlargedLength*2-10,'stroke':'gray','stroke-width':'.5'});
        	startDate = secondaryContainer.append('text').attr({'x':0,'y':enlargedLength*2-10}).style('font-size',9);
			endDate = secondaryContainer.append('text').attr({'x':450,'y':enlargedLength*2-10}).style('font-size',9);

			/* Calling it direcly with getData by the user*/
        	//this.getData();

		};
		this.getData = function(title){
			if(title){
				postDict['titles'] = title;
			}
			if (gettingDataFlag && rvContinueHash){
				//console.log('postDict',postDict);
        		$.getJSON(url,postDict,function(data){
            		if ('query-continue' in data){
                    	rvContinueHash = data['query-continue'].revisions.rvcontinue;
                    	postDict['rvcontinue'] = rvContinueHash;
                	}
            		else{
                    	rvContinueHash = null;
            		}
	            console.log(rvContinueHash);
	            var resultKey = Object.keys(data.query.pages);
	            var revData = data.query.pages[resultKey].revisions;
	            completeRevData = completeRevData.concat(revData);
	            parsedData = that.parseData(completeRevData);
	            that.fixScales();
	            that.addData(primaryGraph,completeRevData,yscale);
	            that.callBrush();
	            gettingDataFlag = true;
        		});
        		gettingDataFlag = false;
        	}
        	else{
            	return;
        	}
		};

		this.cleanUp = function () {
			  completeRevData = [];
			  rvContinueHash = true;
			  postDict ={
		        rvdir:'older',
		        format:'json',
		        action:'query',
		        prop:'revisions',
				titles:'',
		        rvprop:'user|timestamp|flags|ids|size',
		        rvlimit:'max',
	    	};
		};
		//Cleanup big time
		/** To highlight the revision currently being animated **/
		this.modifySecondryGraph = function(field,value,color) {
			newGraph.filter(function(){return true;})
				.style({'fill': function(d,i){
							return d.dir == 'p' ? 'blue':'red' ;}
					});
			newGraph.filter(function(d){
				return d[field] == value;
			}).style('fill',color);
		};
		//Cleanup big time
		/** To get the list of revisions selected in the slider **/
		this.getSelection = function () {
			var brushExtent = brush.extent();
			var start = Math.floor(brushExtent[0]/that.barGraphBarwidth);
			var end = Math.ceil(brushExtent[1]/that.barGraphBarwidth);
			return completeRevData.slice(start,end);
		};
		this.getSecondrySliderSelection = function (i){
			return i ? that.getSelection().slice(0,i+1).reverse():that.getSelection().reverse();
		};
		this.wikiNameSpace = function (language) {
	  		usingLanguageNamespace = language;
	  		baseUrl = 'https://'+language+'.wikipedia.org/w/api.php?callback=?';
		};

		this.parseData = function(data){
	        data.forEach(function(d,i,array){
	                if(array[i+1]){
	                    d.editSize = d.size - array[i+1].size;
	                    if (i == 0 ){
	                        d.timeDiff = 0;
	                    }
	                    else{
	                        d.timeDiff = Math.floor((timeParse.parse(array[i-1].timestamp) - timeParse.parse(d.timestamp) ) /86400000);
	                    }

	                    if (d.editSize >= 0){
	                        d.dir = 'p';
	                    }
	                    else{
	                       d.dir = 'n';
	                       d.editSize = d.editSize * -1;
	                    }
	                }
	                else{
	                    d.editSize = null;
	                    d.timeDiff = null;
	                }
	                //console.log(d.editSize);
	            if (d.parentid == 0){
	                d.editSize = d.size;
	                d.timeDiff = 0;
	                d.dir = 'p';
	            }
	            if ('minor' in d){
	                d.minor = true;
	            }
	            else{
	                d.minor = false;
	            }
	            d.date = timeParse.parse(d.timestamp);
	            });

        		data.svgWidth = data.length*that.barGraphBarwidth < 100 ? 100 : data.length*that.barGraphBarwidth;
        		data.yscale = [0,d3.max(data, function(d) { return d.editSize; })];

   		};

   		this.fixScales = function (){
	        if(xscale == null){
				yscale = d3.scale.pow().exponent(.4).domain([0,d3.max(completeRevData, function(d) { return d.editSize; })]).range([2,outerLength]);
				yscale2 = d3.scale.pow().exponent(.4).domain([0,d3.max(completeRevData, function(d) { return d.editSize; })]).range([2,enlargedLength]);
				xscale = d3.scale.linear().domain([0,completeRevData.svgWidth]).range([0,completeRevData.svgWidth]);
				diffscale = d3.scale.linear().domain([0,d3.max(completeRevData, function(d) { return d.timeDiff; })]).range([0,10]);
			}
	        else{
				yscale.domain([0,d3.max(completeRevData, function(d) { return d.editSize; })]);
				yscale2.domain([0,d3.max(completeRevData, function(d) { return d.editSize; })]);
				xscale.domain([0,completeRevData.svgWidth]).range([0,completeRevData.svgWidth]);
				diffscale.domain([0,d3.max(completeRevData, function(d) { return d.timeDiff; })]);
	        }
	        svg.attr('width',completeRevData.svgWidth);
    	};

    	this.addData = function (rect,data,yscale){
	        bars = rect.selectAll('rect').data(data);
	        bars.enter().append("rect");
	        bars.attr({
                        'x':function(d,i){ return i * that.barGraphBarwidth; },
                        'y':function(d,i){ return d.dir == 'p' ? outerLength - yscale(d.editSize) : outerLength; },
                        'width':that.barGraphBarwidth,
                        'height':function(d,i){ return yscale(d.editSize); },
                        'class':function(d,i){ return d.dir == 'p' ? 'blue':'red'; },
                        'timestamp':function(d){ return d.timestamp; }
                        });
	        bars.exit().remove();
    	};

    	this.callBrush = function (){
	        if (brush != null){
		        brush.x(xscale);
		        d3.select('#primaryBrush').call(brush);
			}
			else{
		            brush = d3.svg.brush().x(xscale).extent([10, 50]).on("brush", brushmove);
		            var brushg = svgBox.append("g").attr("class", "brush")
		                                    .attr("id","primaryBrush")
		                                    .call(brush);
		            brushg.selectAll("rect").attr("height", 100)
		                                    .attr("y",0);
		            //Fix it
		            //brushmove();
		            temp();
			}
		};
		this.handleScroll = function (){
        	//d3.select('#outer').transition().property('scrollLeft',brush.extent()[0]);
         	$('#outer').scrollLeft(brush.extent()[0]);
   		};
   		this.fixWidth = function (width){
			d3.select('#enlarged').style('width',width+'px').select('svg').style('width',width).select('line').attr('x2',width);
			endLine.attr({'x1':width,'x2':width});
    	};
		function brushmove(){
	        var brushExtent = brush.extent();
	        slid_s = d3.event.target.extent();
	        if(slid_s[1] - slid_s[0] < 100){

		        /* the secondary slider */
		        temp();

		        if (brushExtent[1]> completeRevData.length*that.barGraphBarwidth - 50){
		            that.getData();
		        }
	        }
	        else{
	            d3.event.target.extent([slid_s[0],slid_s[0]+95]); d3.event.target(d3.select(this));
	        }
	        /*Calling Primary Slider callback*/
			if (that.primarySliderCallback){
				that.primarySliderCallback();
			}
    	}
    	function temp(){
    			var brushExtent = brush.extent();
	    		 	var new_graph = completeRevData.slice(brushExtent[0]/that.barGraphBarwidth,brushExtent[1]/that.barGraphBarwidth );
	    		 	if (new_graph.length){
				        var diffScaleAbs = 0;
						var lastX = 0;
				        //console.log(brushExtent);
				        newGraph = secondaryGraph.selectAll("rect").data(new_graph);
				        newGraph.enter().append("rect");
				        newGraph.attr("x",function(d,i){
				            diffScaleAbs += d.timeDiff*3;
					    	lastX = diffScaleAbs +  i*that.enlargedBarGraphBarwidth;
				            return lastX;
						})
				        .attr("y",function(d,i){
							return d.dir == 'p' ? enlargedLength - yscale2(d.editSize) : enlargedLength;})
						.attr("width",that.enlargedBarGraphBarwidth)
						.attr("height",function(d){ return yscale2(d.editSize); })
						.attr("class",function(d){ return d.dir=='p'?'pointer blue':'pointer red'; })
						.attr("timeDiff",function(d){ return d.timeDiff; })
						.attr("title",function(d){ return d.user; })
						.attr("number",function(d,i){ return i; })
						.on("mouseover", function(d) {
							tooltipDiv.transition().duration(200).style("opacity", .9);
							bars.filter(function(d){ return d.user == hoverUser; })
							.style({'fill':'steelblue'});
						    hoverUser = d.user;
				            tooltipDiv.html(d.user + "<br/>"  + d.date)
				                .style("left", (d3.event.pageX) + "px")
				                .style("top", (d3.event.pageY - 28) + "px");
				            bars.filter(function(d){
								return d.user == hoverUser;
								}).style({'fill':'orange'});
						})
				        .on("mouseout", function(d) {
				            tooltipDiv.transition()
				                .duration(500)
				                .style("opacity", 0);
				        })
				        .on("click", function(d,i){
							if (that.secondrySliderCallback){
								var selectedEdits = that.getSecondrySliderSelection(i);
								that.secondrySliderCallback(selectedEdits,1);
							}
				        })
				       .style({'fill': function(d,i){
							return d.dir == 'p' ? 'blue':'red' ;}
						});

						newGraph.exit().remove();
						startDate.text(timeParse.parse(new_graph[0].timestamp).toDateString().slice(4));
						endDate.text(timeParse.parse(new_graph[new_graph.length-1].timestamp).toDateString().slice(4)).attr({'x':lastX-50});
						that.fixWidth(lastX);
				}
    	}
    	return this;
    }<|endoftext|>


<commit_before><commit_msg>fix(spells) tweak text<commit_after><|endoftext|>


<commit_before>var asn_cnt   = null;
var asn_uuid  = null;
var tst_cnt   = null;
var tst_uuid  = null;
var fle_uuids = null;
var sub_uuid  = null;
var run_uuid  = null;
var timeout = null;
var ladda_submit = null;

function add_option_alpha(select, option) {
    var inserted = false;
    select.children("option").each(function() {
	if (option.text() < this.text) {
	    console.log("Inserting " + option.text());
	    option.insertBefore(this);
	    inserted = true;
	    return false;
        }
    });
    if ( inserted == false ) {
	console.log("Appending " + option.text());
	select.append(option);
	inserted = true;
    }
}

function submit_onload() {
    ladda_submit = Ladda.create(document.querySelector("button#submit"));
    assignments_get_submitable(update_asn_list, setup_error_callback);
}

function update_asn_list(data, status) {
    $("select#assignment").empty();
    var assignments = data.assignments;
    asn_cnt = assignments.length;
    console.log("assignments = " + assignments);
    console.log("asn_cnt = " + asn_cnt);
    if(asn_cnt > 0) {
        $.each(assignments, function(key, value) {
            var uuid = value;
            assignment_get(update_asn_list_item, setup_error_callback, uuid);
        });
    }
    else {
	var option = $("<option>", { value : ""}).text("No Assignments Accepting Submissions");
        $("select#assignment").append(option);
        $("select#assignment").prop("disabled", true);
    }
}

function update_asn_list_item(data, status) {
    var keys = Object.keys(data);
    var uuid = keys[0];
    var assignment = data[uuid];
    var option = $("<option>", { value : uuid}).text(assignment.name);
    var select = $("select#assignment");
    add_option_alpha(select, option);
    if ($("select#assignment option").size() == asn_cnt) {
	$("select#assignment").val($("select#assignment option:last").val());
        $("select#assignment").change();
        $("select#assignment").prop("disabled", false);
    }
}

function update_tst_list(data, status) {
    $("select#test").empty();
    var tests = data.tests;
    tst_cnt = tests.length;
    console.log("tests = " + tests);
    console.log("tst_cnt = " + tst_cnt);
    if(tst_cnt > 0) {
        $.each(tests, function(key, value) {
            var uuid = value;
            test_get(update_tst_list_item, setup_error_callback, uuid);
        });
    }
    else {
	var option = $("<option>", { value : ""}).text("No Tests Accepting Submissions");
        $("select#test").append(option);
        $("select#test").prop("disabled", true);
        $("input#file").prop("disabled", true);
        $("button#submit").prop("disabled", true);
    }
    clear_results()
}

function update_tst_list_item(data, status) {
    var keys = Object.keys(data);
    var uuid = keys[0];
    var test = data[uuid];
    var option = $("<option>", { value : uuid}).text(test.name);
    var select = $("select#test");
    add_option_alpha(select, option);
    if ($("select#test option").size() == tst_cnt) {
	$("select#test").val($("select#test option:last").val());
        $("select#test").change();
        $("select#test").prop("disabled", false);
        $("input#file").prop("disabled", false);
        $("button#submit").prop("disabled", false);
    }
}

// Submission Sequence

function upload_fle_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Save Files UUIDs
    fle_uuids = data.files;

    // Create Submission
    console.log("Creating Submission...");
    assignment_submission_create(create_sub_callback, submit_error_callback, asn_uuid);

}

function create_sub_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Save Submission UUID
    sub_uuid = data.submissions[0];

    // Add Files to Submission
    var file_lst = fle_uuids;
    console.log("Adding Files...");
    submission_add_files(add_files_callback, submit_error_callback, sub_uuid, file_lst);

}

function add_files_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Check Files
    console.log("Added files: " + data.files);

    // Launch Test Run
    console.log("Starting Test Run...");
    submission_run_test(run_test_callback, submit_error_callback, sub_uuid, tst_uuid);

}

function run_test_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Save Run UUID
    run_uuid = data.runs[0];

    // Log to Console
    console.log("asn_uuid  = " + asn_uuid);
    console.log("tst_uuid  = " + tst_uuid);
    console.log("fle_uuids = " + fle_uuids);
    console.log("sub_uuid  = " + sub_uuid);
    console.log("run_uuid  = " + run_uuid);

    // Output UUID
    $("span#run_uuid").text(run_uuid);

    // Check Results
    poll_results_callback()

}

function poll_results_callback() {

    // Update Results
    console.log("Getting Run Results...");
    run_get(check_result_callback, submit_error_callback, run_uuid);

}

function check_result_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Extract Results
    var keys = Object.keys(data);
    var uuid = keys[0];
    var run = data[uuid];

    // Update Status
    $("span#run_status").text(run.status);

    if(run.status.indexOf("complete") == 0) {

        // Output Results
        $("span#run_score").text(run.score);
        $("span#run_retcode").text(run.retcode);
        $("pre#run_output").text(run.output);

        // Unlock Form
        $("select#assignment").prop("disabled", false);
        $("select#test").prop("disabled", false);
        $("input#file").prop("disabled", false);
	    ladda_submit.stop();
        $("button#submit").children("span.ladda-label").html("Submit");

    }
    else {

        // Start Polling
        console.log("Waiting...");
        timeout = setTimeout(poll_results_callback, 1000);

    }

}

function update_max_score(data, status) {

    var keys = Object.keys(data);
    var uuid = keys[0];
    var tst = data[uuid];
    $("span#max_score").text(tst.maxscore);

}

function submit_error_callback(xhr, status, error) {

    // Log Error
    console.log("Status: " + status, ", Error: " + error);

    // Update Status
    $("span#run_status").text("API Error: " + error);

    // Output Results
    $("span#run_score").text("N/A");
    $("span#run_retcode").text("N/A");
    $("pre#run_output").text(xhr.responseText);

    // Unlock Form
    $("select#assignment").prop("disabled", false);
    $("select#test").prop("disabled", false);
    $("input#file").prop("disabled", false);
	ladda_submit.stop();
    $("button#submit").children("span.ladda-label").html("Submit");

}

function setup_error_callback(xhr, status, error) {

    // Log Error
    console.log("Status: " + status, ", Error: " + error);

    // Update Status
    $("span#run_status").text("API Error: " + error);

    // Output Results
    $("span#run_score").text("N/A");
    $("span#run_retcode").text("N/A");
    $("pre#run_output").text(xhr.responseText);

}

function clear_results() {
    $("span#run_status").text("TBD");
    $("span#run_score").text("TBD");
    $("span#run_retcode").text("TBD");
    $("span#run_uuid").text("TBD");
    $("pre#run_output").text("Grading output will appear here...");
}

$("select#assignment").change(function() {
    var uuid = $("select#assignment").val();
    if(uuid.length > 0) {
        assignment_tests_get(update_tst_list, setup_error_callback, uuid);
    }
});

$("select#test").change(function() {
    var uuid = $("select#test").val();
    if(uuid.length > 0) {
        test_get(update_max_score, setup_error_callback, uuid);
    }
});

$("form#submitform").submit(function() {

    // Start Button Animation
    ladda_submit.start();
    $("button#submit").children("span.ladda-label").html("Running...");

    // Get Input
    asn_uuid = $("select#assignment").val();
    tst_uuid = $("select#test").val();

    // Validate Data
    if((!asn_uuid) || (asn_uuid.length != 36)) {
        console.log("Valid Assignment UUID Required");
        $("button#submit").prop("disabled", false);
        return false;
    }
    if((!tst_uuid) || (tst_uuid.length != 36)) {
        console.log("Valid Test UUID Required");
        $("button#submit").prop("disabled", false);
        return false;
    }
    if($("input#file").val().length == 0) {
        console.log("Valid File Required");
        $("button#submit").prop("disabled", false);
        return false;
    }

    // Upload File
    var file_name = $("input#file").val();
    console.log("File Name = " + file_name);
    var file_ext = file_name.split('.').pop();
    console.log("File Extension = " + file_ext);
    if(file_ext == "zip") {
        $("input#file").attr('name', 'extract')
    }
    else {
        $("input#file").attr('name', 'submission')
    }
    var form_data = new FormData($('form#submitform')[0]);
    console.log("Submitting File...");
    file_post(upload_fle_callback, submit_error_callback, form_data);

    // Lock Form
    $("select#assignment").prop("disabled", true);
    $("select#test").prop("disabled", true);
    $("input#file").prop("disabled", true);

    // Return
    return false

});
<commit_msg>Added change logging<commit_after>var asn_cnt   = null;
var asn_uuid  = null;
var tst_cnt   = null;
var tst_uuid  = null;
var fle_uuids = null;
var sub_uuid  = null;
var run_uuid  = null;
var timeout = null;
var ladda_submit = null;

function add_option_alpha(select, option) {
    var inserted = false;
    select.children("option").each(function() {
	if (option.text() < this.text) {
	    console.log("Inserting " + option.text());
	    option.insertBefore(this);
	    inserted = true;
	    return false;
        }
    });
    if ( inserted == false ) {
	console.log("Appending " + option.text());
	select.append(option);
	inserted = true;
    }
}

function submit_onload() {
    ladda_submit = Ladda.create(document.querySelector("button#submit"));
    assignments_get_submitable(update_asn_list, setup_error_callback);
}

function update_asn_list(data, status) {
    $("select#assignment").empty();
    var assignments = data.assignments;
    asn_cnt = assignments.length;
    console.log("assignments = " + assignments);
    console.log("asn_cnt = " + asn_cnt);
    if(asn_cnt > 0) {
        $.each(assignments, function(key, value) {
            var uuid = value;
            assignment_get(update_asn_list_item, setup_error_callback, uuid);
        });
    }
    else {
	var option = $("<option>", { value : ""}).text("No Assignments Accepting Submissions");
        $("select#assignment").append(option);
        $("select#assignment").prop("disabled", true);
    }
}

function update_asn_list_item(data, status) {
    var keys = Object.keys(data);
    var uuid = keys[0];
    var assignment = data[uuid];
    var option = $("<option>", { value : uuid}).text(assignment.name);
    var select = $("select#assignment");
    add_option_alpha(select, option);
    if ($("select#assignment option").size() == asn_cnt) {
	$("select#assignment").val($("select#assignment option:last").val());
        $("select#assignment").change();
        $("select#assignment").prop("disabled", false);
    }
}

function update_tst_list(data, status) {
    $("select#test").empty();
    var tests = data.tests;
    tst_cnt = tests.length;
    console.log("tests = " + tests);
    console.log("tst_cnt = " + tst_cnt);
    if(tst_cnt > 0) {
        $.each(tests, function(key, value) {
            var uuid = value;
            test_get(update_tst_list_item, setup_error_callback, uuid);
        });
    }
    else {
	var option = $("<option>", { value : ""}).text("No Tests Accepting Submissions");
        $("select#test").append(option);
        $("select#test").prop("disabled", true);
        $("input#file").prop("disabled", true);
        $("button#submit").prop("disabled", true);
    }
    clear_results()
}

function update_tst_list_item(data, status) {
    var keys = Object.keys(data);
    var uuid = keys[0];
    var test = data[uuid];
    var option = $("<option>", { value : uuid}).text(test.name);
    var select = $("select#test");
    add_option_alpha(select, option);
    if ($("select#test option").size() == tst_cnt) {
	$("select#test").val($("select#test option:last").val());
        $("select#test").change();
        $("select#test").prop("disabled", false);
        $("input#file").prop("disabled", false);
        $("button#submit").prop("disabled", false);
    }
}

// Submission Sequence

function upload_fle_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Save Files UUIDs
    fle_uuids = data.files;

    // Create Submission
    console.log("Creating Submission...");
    assignment_submission_create(create_sub_callback, submit_error_callback, asn_uuid);

}

function create_sub_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Save Submission UUID
    sub_uuid = data.submissions[0];

    // Add Files to Submission
    var file_lst = fle_uuids;
    console.log("Adding Files...");
    submission_add_files(add_files_callback, submit_error_callback, sub_uuid, file_lst);

}

function add_files_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Check Files
    console.log("Added files: " + data.files);

    // Launch Test Run
    console.log("Starting Test Run...");
    submission_run_test(run_test_callback, submit_error_callback, sub_uuid, tst_uuid);

}

function run_test_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Save Run UUID
    run_uuid = data.runs[0];

    // Log to Console
    console.log("asn_uuid  = " + asn_uuid);
    console.log("tst_uuid  = " + tst_uuid);
    console.log("fle_uuids = " + fle_uuids);
    console.log("sub_uuid  = " + sub_uuid);
    console.log("run_uuid  = " + run_uuid);

    // Output UUID
    $("span#run_uuid").text(run_uuid);

    // Check Results
    poll_results_callback()

}

function poll_results_callback() {

    // Update Results
    console.log("Getting Run Results...");
    run_get(check_result_callback, submit_error_callback, run_uuid);

}

function check_result_callback(data, status) {

    // Log Data
    console.log("data = " + JSON.stringify(data));

    // Extract Results
    var keys = Object.keys(data);
    var uuid = keys[0];
    var run = data[uuid];

    // Update Status
    $("span#run_status").text(run.status);

    if(run.status.indexOf("complete") == 0) {

        // Output Results
        $("span#run_score").text(run.score);
        $("span#run_retcode").text(run.retcode);
        $("pre#run_output").text(run.output);

        // Unlock Form
        $("select#assignment").prop("disabled", false);
        $("select#test").prop("disabled", false);
        $("input#file").prop("disabled", false);
	    ladda_submit.stop();
        $("button#submit").children("span.ladda-label").html("Submit");

    }
    else {

        // Start Polling
        console.log("Waiting...");
        timeout = setTimeout(poll_results_callback, 1000);

    }

}

function update_max_score(data, status) {

    var keys = Object.keys(data);
    var uuid = keys[0];
    var tst = data[uuid];
    $("span#max_score").text(tst.maxscore);

}

function submit_error_callback(xhr, status, error) {

    // Log Error
    console.log("Status: " + status, ", Error: " + error);

    // Update Status
    $("span#run_status").text("API Error: " + error);

    // Output Results
    $("span#run_score").text("N/A");
    $("span#run_retcode").text("N/A");
    $("pre#run_output").text(xhr.responseText);

    // Unlock Form
    $("select#assignment").prop("disabled", false);
    $("select#test").prop("disabled", false);
    $("input#file").prop("disabled", false);
	ladda_submit.stop();
    $("button#submit").children("span.ladda-label").html("Submit");

}

function setup_error_callback(xhr, status, error) {

    // Log Error
    console.log("Status: " + status, ", Error: " + error);

    // Update Status
    $("span#run_status").text("API Error: " + error);

    // Output Results
    $("span#run_score").text("N/A");
    $("span#run_retcode").text("N/A");
    $("pre#run_output").text(xhr.responseText);

}

function clear_results() {
    $("span#run_status").text("TBD");
    $("span#run_score").text("TBD");
    $("span#run_retcode").text("TBD");
    $("span#run_uuid").text("TBD");
    $("pre#run_output").text("Grading output will appear here...");
}

$("select#assignment").change(function() {
    var uuid = $("select#assignment").val();
    console.log("Assignment changed to " + uuid)
    if(uuid.length > 0) {
        assignment_tests_get(update_tst_list, setup_error_callback, uuid);
    }
});

$("select#test").change(function() {
    var uuid = $("select#test").val();
    console.log("Test changed to " + uuid)
    if(uuid.length > 0) {
        test_get(update_max_score, setup_error_callback, uuid);
    }
});

$("form#submitform").submit(function() {

    // Start Button Animation
    ladda_submit.start();
    $("button#submit").children("span.ladda-label").html("Running...");

    // Get Input
    asn_uuid = $("select#assignment").val();
    tst_uuid = $("select#test").val();

    // Validate Data
    if((!asn_uuid) || (asn_uuid.length != 36)) {
        console.log("Valid Assignment UUID Required");
        $("button#submit").prop("disabled", false);
        return false;
    }
    if((!tst_uuid) || (tst_uuid.length != 36)) {
        console.log("Valid Test UUID Required");
        $("button#submit").prop("disabled", false);
        return false;
    }
    if($("input#file").val().length == 0) {
        console.log("Valid File Required");
        $("button#submit").prop("disabled", false);
        return false;
    }

    // Upload File
    var file_name = $("input#file").val();
    console.log("File Name = " + file_name);
    var file_ext = file_name.split('.').pop();
    console.log("File Extension = " + file_ext);
    if(file_ext == "zip") {
        $("input#file").attr('name', 'extract')
    }
    else {
        $("input#file").attr('name', 'submission')
    }
    var form_data = new FormData($('form#submitform')[0]);
    console.log("Submitting File...");
    file_post(upload_fle_callback, submit_error_callback, form_data);

    // Lock Form
    $("select#assignment").prop("disabled", true);
    $("select#test").prop("disabled", true);
    $("input#file").prop("disabled", true);

    // Return
    return false

});
<|endoftext|>