From 74c7c640151b32a825df0f373b03a808cdad3d62 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Tue, 21 Feb 2017 20:34:18 +0000
Subject: [PATCH] [client] Refactor OtherClient data into class.

---
 Nibriboard/ClientFiles/BoardWindow.js         |  26 +-
 Nibriboard/ClientFiles/OtherClient.js         |  37 +++
 Nibriboard/ClientFiles/Utilities/Rectangle.js |  98 ++++++++
 Nibriboard/ClientFiles/Utilities/Vector.js    | 228 ++++++++++++++++++
 4 files changed, 381 insertions(+), 8 deletions(-)
 create mode 100644 Nibriboard/ClientFiles/OtherClient.js
 create mode 100644 Nibriboard/ClientFiles/Utilities/Rectangle.js
 create mode 100644 Nibriboard/ClientFiles/Utilities/Vector.js

diff --git a/Nibriboard/ClientFiles/BoardWindow.js b/Nibriboard/ClientFiles/BoardWindow.js
index 0719fa5..4fedb69 100644
--- a/Nibriboard/ClientFiles/BoardWindow.js
+++ b/Nibriboard/ClientFiles/BoardWindow.js
@@ -8,6 +8,7 @@ window.panzoom = require("pan-zoom");
 // Our files
 import RippleLink from './RippleLink';
 import ViewportSyncer from './ViewportSyncer';
+import OtherClient from './OtherClient';
 import { get } from './Utilities';
 
 class BoardWindow extends EventEmitter
@@ -164,12 +165,11 @@ class BoardWindow extends EventEmitter
 	{
 		context.save();
 		
-		for (let [clientId, clientData] of this.otherClients)
+		for (let [otherClientId, otherClient] of this.otherClients)
 		{
 			// TODO: Filter rendering by working out if this client is actually inside our viewport or not here
-			// TODO: Refactor the other client information out into another class.
 			context.save();
-			context.translate(clientData.AbsCursorPosition.X, clientData.AbsCursorPosition.Y);
+			context.translate(otherClient.CursorPosition.x, otherClient.CursorPosition.y);
 			
 			context.beginPath();
 			// Horizontal line
@@ -179,7 +179,7 @@ class BoardWindow extends EventEmitter
 			context.moveTo(0, -this.otherCursorRadius);
 			context.lineTo(0, this.otherCursorRadius);
 			
-			context.strokeStyle = clientData.Colour;
+			context.strokeStyle = otherClient.Colour;
 			context.lineWidth = this.otherCursorWidth
 			context.stroke();
 			
@@ -225,16 +225,26 @@ class BoardWindow extends EventEmitter
 		this.sidebar.style.borderTopColor = this.Colour;
 	}
 	
+	/**
+	 * Handles peer update messages recieved from the server via the RippleLink.
+	 */
 	handlePeerUpdates(message) {
 		// Update our knowledge about other clients
 		for (let otherClient of message.ClientStates) {
 			// If this client is new, emit an event about it
-			if(!this.otherClients.has(otherClient.Id))
+			if(!this.otherClients.has(otherClient.Id)) {
 				this.emit("OtherClientConnect", otherClient);
-			else // If not, emit a normal update message about it
+				
+				// Convert the raw object into a class instance
+				let otherClientObj = new OtherClient();
+				otherClientObj.Id = otherClient.Id;
+			}
+			else { // If not, emit a normal update message about it
 				this.emit("OtherClientUpdate", otherClient);
-			// Store the information for later
-			this.otherClients.set(otherClient.Id, otherClient);
+			} 
+			
+			// Get the OtherClient instance to pull in the rest of the data
+			this.otherClients.get(otherClient.Id).update(otherClient);
 		}
 	}
 }
diff --git a/Nibriboard/ClientFiles/OtherClient.js b/Nibriboard/ClientFiles/OtherClient.js
new file mode 100644
index 0000000..c708ea4
--- /dev/null
+++ b/Nibriboard/ClientFiles/OtherClient.js
@@ -0,0 +1,37 @@
+"use strict";
+
+import Vector from './Utilities/Vector';
+import Rectangle from './Utilities/Rectangle';
+
+class OtherClient
+{
+	constructor()
+	{
+		// The unique id of this client.
+		this.Id = -1;
+		// The name of this client.
+		this.Name = "";
+		// The colour the server assigned to this client.
+		this.Colour = "green";
+		// The position of this clients' cursor.
+		this.CursorPosition = new Vector(0, 0);
+		// The position and dimensions of this client's viewport.
+		this.Viewport = Rectangle.Zero.clone();
+	}
+	
+	update(data) {
+		// Update our local copy of this clients' name and colour (just in case)
+		this.Name = data.Name;
+		this.Colour = data.Colour;
+		// Update the cursor position
+		this.CursorPosition.x = data.CursorPosition.X;
+		this.CursorPosition.y = data.CursorPosition.Y;
+		// Update the viewport
+		this.Viewport.x = data.Viewport.X;
+		this.Viewport.y = data.Viewport.X;
+		this.Viewport.width = data.Viewport.Width;
+		this.Viewport.height = data.Viewport.Height;
+	}
+}
+
+export default OtherClient;
diff --git a/Nibriboard/ClientFiles/Utilities/Rectangle.js b/Nibriboard/ClientFiles/Utilities/Rectangle.js
new file mode 100644
index 0000000..b25b995
--- /dev/null
+++ b/Nibriboard/ClientFiles/Utilities/Rectangle.js
@@ -0,0 +1,98 @@
+"use strict";
+
+import Vector from './Vector';
+
+/// <summary>
+/// Represents a rectangle in 2D space.
+/// </summary>
+public class Rectangle
+{
+	/**
+	 * The top-left corner of the rectangle.
+	 * @returns {Vector}
+	 */
+	get TopLeft() {
+		return new Vector(this.x, this.y);
+	}
+	
+	/**
+	 * The top-right corner of the rectangle.
+	 * @returns {Vector}
+	 */
+	get TopRight() {
+		return new Vector(this.x + this.width, this.y);
+	}
+	
+	/**
+	 * The bottom-left corner of the rectangle.
+	 * @returns {Vector}
+	 */
+	get BottomLeft() {
+		return new Vector(this.x, this.y + this.height);
+	}
+	
+	/**
+	 * The bottom-right corner of the rectangle.
+	 */
+	get BottomRight() {
+		return new Vector(this.x + this.width, this.y + this.height);
+	}
+	
+	/**
+	 * The Y coordinate of the top of the rectangle.
+	 * @returns {Number}
+	 */
+	get Top {
+		return this.y;
+	}
+	
+	/**
+	 * The Y coordinate of the bottom of the rectangle.
+	 * @returns {Number}
+	 */
+	get Bottom {
+		return this.y + this.height;
+	}
+	
+	/**
+	 * The X coordinate of the left side of the rectangle.
+	 * @returns {Number}
+	 */
+	get Left() {
+		return this.x;
+	}
+	
+	/**
+	 * The X coordinate of the right side of the rectangle.
+	 * @returns {Number}
+	 */
+	get Right() {
+		return this.x + this.width;
+	}
+
+	constructor(x, y, width, height)
+	{
+		this.x = x;
+		this.y = y;
+		this.width = width;
+		this.height = height;
+	}
+	
+	/**
+	 * Returns a copy of this rectangle that can be safely edited without affecting the original.
+	 * @returns {Rectangle}
+	 */
+	clone()
+	{
+		return new Rectangle(this.x, this.y, this.width, this.height);
+	}
+}
+
+/**
+ * A rectangle with all it's values initalised to zero.
+ * Don't forget to clone it before editing!
+ * @type {Rectangle}
+ */
+Rectangle.Zero = new Rectangle(0, 0, 0, 0);)
+
+export default Rectangle;
diff --git a/Nibriboard/ClientFiles/Utilities/Vector.js b/Nibriboard/ClientFiles/Utilities/Vector.js
new file mode 100644
index 0000000..82432df
--- /dev/null
+++ b/Nibriboard/ClientFiles/Utilities/Vector.js
@@ -0,0 +1,228 @@
+"use strict";
+
+/******************************************************
+ ************** Simple ES6 Vector Class  **************
+ ******************************************************
+ * Author: Starbeamrainbowlabs
+ * Twitter: @SBRLabs
+ * Email: feedback at starbeamrainbowlabs dot com
+ * 
+ * From https://gist.github.com/sbrl/69a8fa588865cacef9c0
+ ******************************************************
+ * Originally written for my 2D Graphics ACW at Hull
+ * University.
+ ******************************************************
+ * Changelog
+ ******************************************************
+ * 19th December 2015:
+	* Added this changelog.
+ * 28th December 2015:
+	* Rewrite tests with klud.js + Node.js
+ * 30th January 2016:
+	* Tweak angleFrom function to make it work properly.
+ * 31st January 2016:
+	* Add the moveTowards function.
+	* Add the minComponent getter.
+	* Add the maxComponent getter.
+	* Add the equalTo function.
+	* Tests still need to be written for all of the above.
+ * 19th September 2016:
+	* Added Vector support to the multiply method.
+ */
+
+class Vector {
+	// Constructor
+	constructor(inX, inY) {
+		if(typeof inX != "number")
+			throw new Error("Invalid x value.");
+		if(typeof inY != "number")
+			throw new Error("Invalid y value.");
+		
+		// Store the (x, y) coordinates
+		this.x = inX;
+		this.y = inY;
+	}
+
+	/**
+	 * Add another vector to this vector.
+	 * @param {Vector} v The vector to add.
+	 * @return {Vector}   The current vector. useful for daisy-chaining calls.
+	 */
+	add(v) {
+		this.x += v.x;
+		this.y += v.y;
+
+		return this;
+	}
+
+	/**
+	 * Take another vector from this vector.
+	 * @param  {Vector} v The vector to subtrace from this one.
+	 * @return {Vector}   The current vector. useful for daisy-chaining calls.
+	 */
+	subtract(v) {
+		this.x -= v.x;
+		this.y -= v.y;
+
+		return this;
+	}
+
+	/**
+	 * Divide the current vector by a given value.
+	 * @param  {number} value The value to divide by.
+	 * @return {Vector}	   The current vector. Useful for daisy-chaining calls.
+	 */
+	divide(value) {
+		if(typeof value != "number")
+			throw new Error("Can't divide by non-number value.");
+		
+		this.x /= value;
+		this.y /= value;
+
+		return this;
+	}
+
+	/**
+	 * Multiply the current vector by a given value.
+	 * @param  {(number|Vector)} value The number (or Vector) to multiply the current vector by.
+	 * @return {Vector}	   The current vector. useful for daisy-chaining calls.
+	 */
+	multiply(value) {
+		if(value instanceof Vector)
+		{
+			this.x *= value.x;
+			this.y *= value.y;
+		}
+		else if(typeof value == "number")
+		{
+			this.x *= value;
+			this.y *= value;
+		}
+		else
+			throw new Error("Can't multiply by non-number value.");
+		
+		return this;
+	}
+	
+	/**
+	 * Move the vector towards the given vector by the given amount.
+	 * @param  {Vector} v      The vector to move towards.
+	 * @param  {number} amount The distance to move towards the given vector.
+	 */
+	moveTowards(v, amount)
+	{
+		// From http://stackoverflow.com/a/2625107/1460422
+		var dir = new Vector(
+			v.x - this.x,
+			v.y - this.y
+		).limitTo(amount);
+		this.x += dir.x;
+		this.y += dir.y;
+		
+		return this;
+	}
+
+	/**
+	 * Limit the length of the current vector to value without changing the
+	 * direction in which the vector is pointing.
+	 * @param  {number} value The number to limit the current vector's length to.
+	 * @return {Vector}	   The current vector. useful for daisy-chaining calls.
+	 */
+	limitTo(value) {
+		if(typeof value != "number")
+			throw new Error("Can't limit to non-number value.");
+		
+		this.divide(this.length);
+		this.multiply(value);
+
+		return this;
+	}
+
+	/**
+	 * Return the dot product of the current vector and another vector.
+	 * @param  {Vector} v   The other vector we should calculate the dot product with.
+	 * @return {Vector}	 The current vector. useful for daisy-chaining calls.
+	 */
+	dotProduct(v) {
+		return (this.x * v.x) + (this.y * v.y);
+	}
+
+	/**
+	 * Calculate the angle, in radians, from north to another vector.
+	 * @param  {Vector} v The other vector to which to calculate the angle.
+	 * @return {Vector}	 The current vector. useful for daisy-chaining calls.
+	 */
+	angleFrom(v) {
+		// From http://stackoverflow.com/a/16340752/1460422
+		var angle = Math.atan2(v.y - this.y, v.x - this.x) - (Math.PI / 2);
+		angle += Math.PI/2;
+		if(angle < 0) angle += Math.PI * 2;
+		return angle;
+	}
+
+	/**
+	 * Clones the current vector.
+	 * @return {Vector} A clone of the current vector. Very useful for passing around copies of a vector if you don't want the original to be altered.
+	 */
+	clone() {
+		return new Vector(this.x, this.y);
+	}
+
+	/*
+	 * Returns a representation of the current vector as a string.
+	 * @returns {string} A representation of the current vector as a string.
+	 */
+	toString() {
+		return `(${this.x}, ${this.y})`;
+	}
+	
+	/**
+	 * Whether the vector is equal to another vector.
+	 * @param  {Vector} v The vector to compare to.
+	 * @return {boolean}  Whether the current vector is equal to the given vector.
+	 */
+	equalTo(v)
+	{
+		if(this.x == v.x && this.y == v.y)
+			return true;
+		else
+			return false;
+	}
+
+	/**
+	 * Get the unit vector of the current vector - that is a vector poiting in the same direction with a length of 1. Note that this does *not* alter the original vector.
+	 * @return {Vector} The current vector's unit form.
+	 */
+	get unitVector() {
+		var length = this.length;
+		return new Vector(
+		this.x / length,
+		this.y / length);
+	}
+
+	/**
+	 * Get the length of the current vector.
+	 * @return {number} The length of the current vector.
+	 */
+	get length() {
+		return Math.sqrt((this.x * this.x) + (this.y * this.y));
+	}
+	
+	/**
+	 * Get the value of the minimum component of the vector.
+	 * @return {number} The minimum component of the vector.
+	 */
+	get minComponent() {
+		return Math.min(this.x, this.y);
+	}
+	
+	/**
+	 * Get the value of the maximum component of the vector.
+	 * @return {number} The maximum component of the vector.
+	 */
+	get maxComponent() {
+		return Math.min(this.x, this.y);
+	}
+}
+
+export default Vector;
