<commit_before>// shapnal
// Shape Contour Signal Graph
// nhmood @ [gooscode labs] - 2013
// HTML5 Canvas + JS

// [todo] - Use more complex method to find center of shape
//		  - Currently just using click but maybe use skeleton method
//		  - http://stackoverflow.com/questions/1203135/what-is-the-fastest-way-to-find-the-center-of-an-irregularly-shaped-polygon
//		  - Or use simple center of 2d mass with uniform density

// cid = canvas id
// cheight = canvas height
// cwidth = canvas width
// lstyle = line style
// lwidth = line width
// ccol = center color
// crad = center radius

function Shape(cid, cheight, cwidth, lstyle, lwidth, ccol, crad)  {
	// Grab canvas by ID, use "shape" as default
	this.cID = typeof(cid) === "undefined" ? "shape" : cid;
	this.canvas = document.getElementById(this.cID);

	// Add event handlersr for mouse actions
	// Use .bind(this) to scope "this" to Shape not canvas
	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
	this.canvas.addEventListener("mousedown", this.mouseDown.bind(this));
	this.canvas.addEventListener("mousemove", this.mouseMove.bind(this));
	this.canvas.addEventListener("mouseup"  , this.mouseUp.bind(this));

	// Set size of canvas, use 400x400 as default
	this.canvas.width  = typeof(cwidth)  === "undefined" ? 400 : cwidth;
	this.canvas.height = typeof(cheight) === "undefined" ? 400 : cheight;

	// Get context of canvas for path drawing
	this.ctx = this.canvas.getContext("2d");

	this.ctx.strokeStyle = typeof(lstyle) === "undefined" ? "black" : lstyle;
	this.ctx.lineWidth   = typeof(lwidth) === "undefined" ?    3    : lwidth;

	// Create contour object to hold contour, gets populated from mouse events
	this.contour = new Contour();

	// ==================
	// Internal flags
	// ==================

	// drawEnable - Flag for when mouse movement registers drawing
	// drawComplete - Flag for when drawing is completed
	this.drawEnable = 0;
	this.drawComplete = 0;

	// Center point for shape that line is drawn from
	this.centerValid = 0;
	this.centerX = 0;
	this.centerY = 0;

	this.ccol = typeof(ccol) === "undefined" ? "FF0000" : ccol;
	this.crad = typeof(crad) === "undefined" ?     5    : crad;

	this.counter = 0;

}

Shape.prototype.mouseDown = function(e){
	// If we haven't drawn our shape yet, draw our shape!
	if (!this.drawComplete){
		// Enable drawing for mousemove listener
		this.drawEnable = 1;

		// Add starting points to contour
		this.contour.addPoint(e.offsetX, e.offsetY);
		this.ctx.moveTo(e.offsetX, e.offsetY);
		console.log("mousedowndraw");
	}
	// If shape is drawn and mouse is clicked, register new center point
	else {
		// We now have a valid center point
		this.centerValid = 1;

		// Set center for this instance
		this.centerX = e.offsetX;
		this.centerY = e.offsetY;

		// Clear canvas, restore original shape, and place new center
		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
		this.drawContour();

		// Draw center point
		this.ctx.fillStyle = this.ccol;
		this.ctx.fillRect(this.centerX - this.crad/2, this.centerY - this.crad/2, this.crad, this.crad);
	}
};

Shape.prototype.mouseMove = function(e){
	// If drawing is enabled, draw the line\
	if (this.drawEnable){
		console.log("mousemovedraw");
		// Store coords at mouse location
		var x = e.offsetX;
		var y = e.offsetY;

		// Add this point to our contour
		this.contour.addPoint(x, y);

		// Draw the point itself using a path
		// We need this to be done instantaneously for the user
		// So using a path is better than drawing each individual contour point
		// We will draw actual contour points as rectangles after full path is done
		this.ctx.lineTo(x, y);
		this.ctx.stroke();
	}
};

Shape.prototype.mouseUp = function(e){
	if (this.drawEnable){
		// Store coords at mouse location
		var x = e.offsetX;
		var y = e.offsetY;

		// Need a sufficiently sized shape
		// Also prevents accidental clicks
		if (this.contour.points.length > 10){
			// Add this current point to connect last to this
			// Then add initial point again to get contour for closePath
			this.contour.addPoint(x, y);
			this.contour.addPoint(this.contour.startX, this.contour.startY);

			// Finish line contour and save it
			this.ctx.lineTo(this.contour.startX, this.contour.startY);
			this.ctx.stroke();
			this.ctx.closePath();

			// Draw contour using actual rectangles then save context so we can
			// redraw this every frame next time
			this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
			this.drawContour();

			this.drawEnable = 0;
			this.drawComplete = 1;
		}
		// If not big enough, reset canvas, allow drawing, and reset params
		else {
			this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
			this.contour = new Contour();
			this.drawEnable = 0;
			this.drawComplete = 0;
		}
	}
};

Shape.prototype.drawContour = function(e){
	this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
	// Check if we have a contour to use
	// Short circuit with || will make sure second statement doesn't cause error
	if (typeof this.contour === 'undefined' || this.contour.points.length == 0){
		console.log("Uh oh, looks like there isn't a contour to draw");
		return;
	}

	// If we have valid contour, draw it
	for (var i = 0; i < this.contour.points.length; i++){
		this.ctx.fillStyle = "#000000";
		// Had to shift both dimensions by 2 pixels to line up with path line???
		this.ctx.fillRect(this.contour.points[i][0]-2, this.contour.points[i][1]-2, this.ctx.lineWidth, this.ctx.lineWidth+1);
	}
}

Shape.prototype.drawCenter = function(e){
	this.ctx.fillStyle = this.ccol;
	this.ctx.fillRect(this.centerX - this.crad/2, this.centerY - this.crad/2, this.crad, this.crad);
}

function Contour(startx, starty) {
	this.startX = typeof(startx) === "undefined" ? 0 : startx;
	this.startY = typeof(starty) === "undefined" ? 0 : starty;
	this.endX = 0;
	this.endY = 0;

	this.points = new Array();
}

Contour.prototype.addPoint = function(x, y){
	// If this is the first point to be added, set as startXY also
	if (this.points.length == 0){
		this.points.push([x, y]);
		this.startX = x;
		this.startY = y;
	}
	else {
		// Get the last points in the array
		// Get interpolated array using x, y values
		// Concatenate to points array
		var prev = this.points[this.points.length - 1];
		var interp = this.pointInterp(prev[0], prev[1], x, y);
		this.points = 	this.points.concat(interp);

	}
};

Contour.prototype.pointInterp = function(startX, startY, endX, endY){
	// To interpolate, draw straight line between given points
	// Use y = mx + b to calculate all points in between

	var interp = new Array();

	// Make sure we don't divide by 0!
	var m = 0;
	if (endX != startX){
		m = (endY - startY) / (endX - startX);
	}

	var b = endY - (m * endX);
	// Interpolate points for axis with smallest difference
	// If xdif is bigger than ydif, traverse x and get all the y points
	if (Math.abs(endX - startX) > Math.abs(endY - startY)){
		// Points need to be generated in the correct direction
		// If x2 is larger, then we are moving right, else left
		var xDir = endX > startX ? 1 : -1;

		// Probably shouldn't use != in for comparison but
		// its the only way to not have separate for statements
		// Add xDir to last point to make inclusive
		var y = 0;
		for (var x = startX; x != (endX + xDir); x += xDir){
			// If slope is inf or 0, then the Y stays the same
			y = startY;
			if (m != 0){
				y = (m * x) + b;
			}

			interp.push([Math.round(x), Math.round(y)]);
		}
	}
	// If xdif is smaller than ydif, traverse y and get all the y points
	else {
		// Points need to be generated in the correct direction
		// If y2 is larger, then we are moving right, else left
		var yDir = endY > startY ? 1 : -1;

		// Probably shouldn't use != in for comparison but
		// its the only way to not have separate for statements
		// Add yDir to last point to make inclusive
		var x = 0;

		for (var y = startY; y != (endY + yDir); y += yDir){

			// If slope is inf or 0, then the X stays the same
			x = startX;
			if (m != 0){
				x = (y - b)/m;
			}
			interp.push([Math.round(x), Math.round(y)]);
		}
	}

	return interp;
}

// Plot class to store/plot the signal
function Plot(shape, cid, cheight, cwidth){
	// Grab canvas by ID, use "plot" as default
	this.cID = typeof(cid) === "undefined" ? "plot" : cid;
	this.canvas = document.getElementById(this.cID);
	this.ctx = this.canvas.getContext('2d');

	// Set size of canvas, use 200, 500 as default
	this.canvas.width  = typeof(cwidth)  === "undefined" ? 200 : cwidth;
	this.canvas.height = typeof(cheight) === "undefined" ? 500 : cheight;
}

// Animation class that handles drawing/rendering
function Animation(shape, plot){
	// Store shape and plot to be associated with this animation internally
	this.shape = shape;
	this.plot = plot;

	// Timeout ID used for start/stop animation (initialize to 0)
	this.timeoutID = 0;

	// Animation settings
	this.renderFPS = 1;		// FPS
	this.pSkip = 5;				// Detail of animation
								// Every nth point to plot
	this.currFrame = 0;			// Global frame (position) to sync plot + shape
	this.contourLength = 0;		// Number of points in contour of shape
}

// Adapted from the Paul Irish & CreativeJS
// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://creativejs.com/resources/requestanimationframe/
Animation.prototype.beginAnimation = function(e){
	// Only allow for animation start if we have a shape and a center
	if ((shape.drawComplete) && (shape.centerValid) ){

		// Populate contour length only when there is a contour to use
		this.contourLength = this.shape.contour.points.length;

		// Set up requestAnimationFrame per browser
		window._requestAnimationFrame = window.requestAnimationFrame	 	 || // Chrome
								 		window.webkitRequestAnimationFrame	 || // Safari
                                 		window.mozRequestAnimationFrame   	 || // Firefox
                                 		window.oRequestAnimationFrame     	 || // Opera
	                             		window.msRequestAnimationFrame    	 || // IE
								 		function(callback){
											window.setTimeout(callback, 1000/60);   // 60fps
								 		}

		// Initiate requestAnimationFrame callback to Animation.animate()
		// Bind call to "this" (this instance of Animation) to allow for
		// proper access of vars
		window._requestAnimationFrame(this.animate.bind(this));

	}
	else {
		console.log("Hey now, draw a shape and a center please!");
	}
}

// Animation handler
Animation.prototype.animate = function(e){

	// Animation Stuff Here!
	console.log("Animating!");

	// Create a timeout for another requestAnimationFrame
	// Binding hell commence...not sure the full reason for these binds but I know the problems they solve
	// First pass this instance of Animation's animate method to the rAF
	// Bind "this" to it or else you get a Uncaught TypeError: Type Error
	// Bind the entire function you pass to rAF to this so it knows what this.animate is

	// Set timeout to 1000/renderFPS which is an Animation parameter to control FPS
	// Store setTimeout ID return so we can cancel if we want
	this.timeoutID = setTimeout(function() {
		window._requestAnimationFrame(this.animate.bind(this));
	}.bind(this), 1000/this.renderFPS);

}

// Cancel next animate() call by clearing the currently stored timeout
Animation.prototype.stopAnimation = function(e){
	window.clearTimeout(this.timeoutID);
	console.log("Stop animating :(");

}

debugTrace = function(e){
	e = e || window.event;
	if (e.keyCode == "37"){
		this.counter++;
		// Restore shape by clearing canvas and restoring
		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
		this.drawContour();
		this.drawCenter();
		// Draw center point
		this.ctx.beginPath();
		this.ctx.moveTo(this.centerX, this.centerY);
		this.ctx.lineTo(this.contour.points[this.counter % this.contour.points.length][0], this.contour.points[this.counter % this.contour.points.length][1]);
		this.ctx.stroke();
		this.ctx.closePath();

		this.counter++;
	}

};

// Run
var shape = new Shape("shape", 400, 400);
var plot = new Plot(shape, "plot", 200, 500);
var animation = new Animation(shape, plot);

// Debug trace
//document.onkeydown = debugTrace.bind(shape);
<commit_msg>Added shape contour animation<commit_after>// shapnal
// Shape Contour Signal Graph
// nhmood @ [gooscode labs] - 2013
// HTML5 Canvas + JS

// [todo] - Use more complex method to find center of shape
//		  - Currently just using click but maybe use skeleton method
//		  - http://stackoverflow.com/questions/1203135/what-is-the-fastest-way-to-find-the-center-of-an-irregularly-shaped-polygon
//		  - Or use simple center of 2d mass with uniform density

// cid = canvas id
// cheight = canvas height
// cwidth = canvas width
// lstyle = line style
// lwidth = line width
// ccol = center color
// crad = center radius

function Shape(cid, cheight, cwidth, lstyle, lwidth, ccol, crad)  {
	// Grab canvas by ID, use "shape" as default
	this.cID = typeof(cid) === "undefined" ? "shape" : cid;
	this.canvas = document.getElementById(this.cID);

	// Add event handlersr for mouse actions
	// Use .bind(this) to scope "this" to Shape not canvas
	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
	this.canvas.addEventListener("mousedown", this.mouseDown.bind(this));
	this.canvas.addEventListener("mousemove", this.mouseMove.bind(this));
	this.canvas.addEventListener("mouseup"  , this.mouseUp.bind(this));

	// Set size of canvas, use 400x400 as default
	this.canvas.width  = typeof(cwidth)  === "undefined" ? 400 : cwidth;
	this.canvas.height = typeof(cheight) === "undefined" ? 400 : cheight;

	// Get context of canvas for path drawing
	this.ctx = this.canvas.getContext("2d");

	this.ctx.strokeStyle = typeof(lstyle) === "undefined" ? "black" : lstyle;
	this.ctx.lineWidth   = typeof(lwidth) === "undefined" ?    3    : lwidth;

	// Create contour object to hold contour, gets populated from mouse events
	this.contour = new Contour();

	// ==================
	// Internal flags
	// ==================

	// drawEnable - Flag for when mouse movement registers drawing
	// drawComplete - Flag for when drawing is completed
	this.drawEnable = 0;
	this.drawComplete = 0;

	// Center point for shape that line is drawn from
	this.centerValid = 0;
	this.centerX = 0;
	this.centerY = 0;

	this.ccol = typeof(ccol) === "undefined" ? "FF0000" : ccol;
	this.crad = typeof(crad) === "undefined" ?     5    : crad;

	this.counter = 0;

}

Shape.prototype.mouseDown = function(e){
	// If we haven't drawn our shape yet, draw our shape!
	if (!this.drawComplete){
		// Enable drawing for mousemove listener
		this.drawEnable = 1;

		// Add starting points to contour
		this.contour.addPoint(e.offsetX, e.offsetY);
		this.ctx.moveTo(e.offsetX, e.offsetY);
		console.log("mousedowndraw");
	}
	// If shape is drawn and mouse is clicked, register new center point
	else {
		// We now have a valid center point
		this.centerValid = 1;

		// Set center for this instance
		this.centerX = e.offsetX;
		this.centerY = e.offsetY;

		// Clear canvas, restore original shape, and place new center
		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
		this.drawContour();

		// Draw center point
		this.ctx.fillStyle = this.ccol;
		this.ctx.fillRect(this.centerX - this.crad/2, this.centerY - this.crad/2, this.crad, this.crad);
	}
};

Shape.prototype.mouseMove = function(e){
	// If drawing is enabled, draw the line\
	if (this.drawEnable){
		console.log("mousemovedraw");
		// Store coords at mouse location
		var x = e.offsetX;
		var y = e.offsetY;

		// Add this point to our contour
		this.contour.addPoint(x, y);

		// Draw the point itself using a path
		// We need this to be done instantaneously for the user
		// So using a path is better than drawing each individual contour point
		// We will draw actual contour points as rectangles after full path is done
		this.ctx.lineTo(x, y);
		this.ctx.stroke();
	}
};

Shape.prototype.mouseUp = function(e){
	if (this.drawEnable){
		// Store coords at mouse location
		var x = e.offsetX;
		var y = e.offsetY;

		// Need a sufficiently sized shape
		// Also prevents accidental clicks
		if (this.contour.points.length > 10){
			// Add this current point to connect last to this
			// Then add initial point again to get contour for closePath
			this.contour.addPoint(x, y);
			this.contour.addPoint(this.contour.startX, this.contour.startY);

			// Finish line contour and save it
			this.ctx.lineTo(this.contour.startX, this.contour.startY);
			this.ctx.stroke();
			this.ctx.closePath();

			// Draw contour using actual rectangles then save context so we can
			// redraw this every frame next time
			this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
			this.drawContour();

			this.drawEnable = 0;
			this.drawComplete = 1;
		}
		// If not big enough, reset canvas, allow drawing, and reset params
		else {
			this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
			this.contour = new Contour();
			this.drawEnable = 0;
			this.drawComplete = 0;
		}
	}
};

Shape.prototype.drawContour = function(e){
	this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
	// Check if we have a contour to use
	// Short circuit with || will make sure second statement doesn't cause error
	if (typeof this.contour === 'undefined' || this.contour.points.length == 0){
		console.log("Uh oh, looks like there isn't a contour to draw");
		return;
	}

	// If we have valid contour, draw it
	for (var i = 0; i < this.contour.points.length; i++){
		this.ctx.fillStyle = "#000000";
		// Had to shift both dimensions by 2 pixels to line up with path line???
		this.ctx.fillRect(this.contour.points[i][0]-2, this.contour.points[i][1]-2, this.ctx.lineWidth, this.ctx.lineWidth+1);
	}
}

Shape.prototype.drawCenter = function(e){
	this.ctx.fillStyle = this.ccol;
	this.ctx.fillRect(this.centerX - this.crad/2, this.centerY - this.crad/2, this.crad, this.crad);
}

Shape.prototype.animate = function(index){
	// Clear canvas and redraw contour
	this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
	this.drawContour();
	this.drawCenter();

	// Draw a line from	center to next point
	this.ctx.beginPath();
	this.ctx.moveTo(this.centerX, this.centerY);
	this.ctx.lineTo(this.contour.points[index][0], this.contour.points[index][1]);
	this.ctx.stroke();
	this.ctx.closePath();

}

function Contour(startx, starty) {
	this.startX = typeof(startx) === "undefined" ? 0 : startx;
	this.startY = typeof(starty) === "undefined" ? 0 : starty;
	this.endX = 0;
	this.endY = 0;

	this.points = new Array();
}

Contour.prototype.addPoint = function(x, y){
	// If this is the first point to be added, set as startXY also
	if (this.points.length == 0){
		this.points.push([x, y]);
		this.startX = x;
		this.startY = y;
	}
	else {
		// Get the last points in the array
		// Get interpolated array using x, y values
		// Concatenate to points array
		var prev = this.points[this.points.length - 1];
		var interp = this.pointInterp(prev[0], prev[1], x, y);
		this.points = 	this.points.concat(interp);

	}
};

Contour.prototype.pointInterp = function(startX, startY, endX, endY){
	// To interpolate, draw straight line between given points
	// Use y = mx + b to calculate all points in between

	var interp = new Array();

	// Make sure we don't divide by 0!
	var m = 0;
	if (endX != startX){
		m = (endY - startY) / (endX - startX);
	}

	var b = endY - (m * endX);
	// Interpolate points for axis with smallest difference
	// If xdif is bigger than ydif, traverse x and get all the y points
	if (Math.abs(endX - startX) > Math.abs(endY - startY)){
		// Points need to be generated in the correct direction
		// If x2 is larger, then we are moving right, else left
		var xDir = endX > startX ? 1 : -1;

		// Probably shouldn't use != in for comparison but
		// its the only way to not have separate for statements
		// Add xDir to last point to make inclusive
		var y = 0;
		for (var x = startX; x != (endX + xDir); x += xDir){
			// If slope is inf or 0, then the Y stays the same
			y = startY;
			if (m != 0){
				y = (m * x) + b;
			}

			interp.push([Math.round(x), Math.round(y)]);
		}
	}
	// If xdif is smaller than ydif, traverse y and get all the y points
	else {
		// Points need to be generated in the correct direction
		// If y2 is larger, then we are moving right, else left
		var yDir = endY > startY ? 1 : -1;

		// Probably shouldn't use != in for comparison but
		// its the only way to not have separate for statements
		// Add yDir to last point to make inclusive
		var x = 0;

		for (var y = startY; y != (endY + yDir); y += yDir){

			// If slope is inf or 0, then the X stays the same
			x = startX;
			if (m != 0){
				x = (y - b)/m;
			}
			interp.push([Math.round(x), Math.round(y)]);
		}
	}

	return interp;
}

// Plot class to store/plot the signal
function Plot(shape, cid, cheight, cwidth){
	// Grab canvas by ID, use "plot" as default
	this.cID = typeof(cid) === "undefined" ? "plot" : cid;
	this.canvas = document.getElementById(this.cID);
	this.ctx = this.canvas.getContext('2d');

	// Set size of canvas, use 200, 500 as default
	this.canvas.width  = typeof(cwidth)  === "undefined" ? 200 : cwidth;
	this.canvas.height = typeof(cheight) === "undefined" ? 500 : cheight;
}

// Animation class that handles drawing/rendering
function Animation(shape, plot){
	// Store shape and plot to be associated with this animation internally
	this.shape = shape;
	this.plot = plot;

	// Timeout ID used for start/stop animation (initialize to 0)
	this.timeoutID = 0;

	// Animation settings
	this.renderFPS = 60;		// FPS
	this.pSkip = 2;				// Detail of animation
								// Every nth point to plot
	this.currFrame = 0;			// Global frame (position) to sync plot + shape
	this.contourLength = 0;		// Number of points in contour of shape
}

// Adapted from the Paul Irish & CreativeJS
// http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://creativejs.com/resources/requestanimationframe/
Animation.prototype.beginAnimation = function(e){
	// Only allow for animation start if we have a shape and a center
	if ((shape.drawComplete) && (shape.centerValid) ){

		// Populate contour length only when there is a contour to use
		this.contourLength = this.shape.contour.points.length;

		// Set up requestAnimationFrame per browser
		window._requestAnimationFrame = window.requestAnimationFrame	 	 || // Chrome
								 		window.webkitRequestAnimationFrame	 || // Safari
                                 		window.mozRequestAnimationFrame   	 || // Firefox
                                 		window.oRequestAnimationFrame     	 || // Opera
	                             		window.msRequestAnimationFrame    	 || // IE
								 		function(callback){
											window.setTimeout(callback, 1000/60);   // 60fps
								 		}

		// Initiate requestAnimationFrame callback to Animation.animate()
		// Bind call to "this" (this instance of Animation) to allow for
		// proper access of vars
		window._requestAnimationFrame(this.animate.bind(this));

	}
	else {
		console.log("Hey now, draw a shape and a center please!");
	}
}

// Animation handler
Animation.prototype.animate = function(e){

	// Animation Stuff Here!
	this.shape.animate(this.currFrame % this.contourLength);
	this.currFrame += this.pSkip;

	// Create a timeout for another requestAnimationFrame
	// Binding hell commence...not sure the full reason for these binds but I know the problems they solve
	// First pass this instance of Animation's animate method to the rAF
	// Bind "this" to it or else you get a Uncaught TypeError: Type Error
	// Bind the entire function you pass to rAF to this so it knows what this.animate is

	// Set timeout to 1000/renderFPS which is an Animation parameter to control FPS
	// Store setTimeout ID return so we can cancel if we want
	this.timeoutID = setTimeout(function() {
		window._requestAnimationFrame(this.animate.bind(this));
	}.bind(this), 1000/this.renderFPS);

}

// Cancel next animate() call by clearing the currently stored timeout
Animation.prototype.stopAnimation = function(e){
	window.clearTimeout(this.timeoutID);
}

debugTrace = function(e){
	e = e || window.event;
	if (e.keyCode == "37"){
		this.counter++;
		// Restore shape by clearing canvas and restoring
		this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
		this.drawContour();
		this.drawCenter();
		// Draw center point
		this.ctx.beginPath();
		this.ctx.moveTo(this.centerX, this.centerY);
		this.ctx.lineTo(this.contour.points[this.counter % this.contour.points.length][0], this.contour.points[this.counter % this.contour.points.length][1]);
		this.ctx.stroke();
		this.ctx.closePath();

		this.counter++;
	}

};

// Run
var shape = new Shape("shape", 400, 400);
var plot = new Plot(shape, "plot", 200, 500);
var animation = new Animation(shape, plot);

// Debug trace
//document.onkeydown = debugTrace.bind(shape);
<|endoftext|>


<commit_before>define(["sprd/data/SprdModel", "sprd/model/Shop", "sprd/model/OrderItem", "js/data/Collection", "sprd/model/Delivery", "sprd/entity/Price", "sprd/model/User"], function(SprdModel, Shop, OrderItem, Collection, Delivery, Price, User) {

    var Shipping = Delivery.Shipping.inherit("sprd.model.Order.Shipping", {
        schema: {
            price: Price,
            priceItem: Price
        }
    });

    return SprdModel.inherit('sprd.model.Order', {
        defaults: {
            shipping: null,
            orderItems: null,
            repayable: false
        },

        schema: {
            shop: Shop,
            user: User,
            orderItems: Collection.of(OrderItem),
            billing: Delivery.Billing,
            shipping: Shipping,
            repayable: Boolean,
            priceTotal: Price
        },

        totalItemsCount: function() {
            var orderItems = this.$.orderItems;
            return orderItems ? orderItems.$items.length : 0;
        }.onChange("orderItems"),

        items: function() {
            return this.$.orderItems;
        }.onChange("orderItems"),

        subTotal: function(type) {
            return this.get('priceItems.' + type || "vatIncluded")
        }.on('change'),

        vatIncluded: function () {
            var vatIncluded = 0;
            if (this.$.orderItems) {
                this.$.orderItems.each(function (orderItem) {
                    vatIncluded += orderItem.totalVatIncluded();
                });
            }
            return vatIncluded;
        }.onChange('orderItems'),

        totalVatIncluded: function () {
            return this.get('priceTotal.vatIncluded');
        }.onChange('priceTotal'),

        totalVatExcluded: function() {
            return this.get('priceTotal.vatExcluded');
        }.onChange('priceTotal'),

        totalVat: function () {
            return Math.max(0, (this.totalVatIncluded() - this.totalVatExcluded()) || 0);
        }.onChange('priceTotal')

    });
});<commit_msg>Revert "DEV-116597 - added user into the Order model"<commit_after>define(["sprd/data/SprdModel", "sprd/model/Shop", "sprd/model/OrderItem", "js/data/Collection", "sprd/model/Delivery", "sprd/entity/Price"], function(SprdModel, Shop, OrderItem, Collection, Delivery, Price) {

    var Shipping = Delivery.Shipping.inherit("sprd.model.Order.Shipping", {
        schema: {
            price: Price,
            priceItem: Price
        }
    });

    return SprdModel.inherit('sprd.model.Order', {
        defaults: {
            shipping: null,
            orderItems: null,
            repayable: false
        },

        schema: {
            shop: Shop,
            orderItems: Collection.of(OrderItem),
            billing: Delivery.Billing,
            shipping: Shipping,
            repayable: Boolean,
            priceTotal: Price
        },

        totalItemsCount: function() {
            var orderItems = this.$.orderItems;
            return orderItems ? orderItems.$items.length : 0;
        }.onChange("orderItems"),

        items: function() {
            return this.$.orderItems;
        }.onChange("orderItems"),

        subTotal: function(type) {
            return this.get('priceItems.' + type || "vatIncluded")
        }.on('change'),

        vatIncluded: function () {
            var vatIncluded = 0;
            if (this.$.orderItems) {
                this.$.orderItems.each(function (orderItem) {
                    vatIncluded += orderItem.totalVatIncluded();
                });
            }
            return vatIncluded;
        }.onChange('orderItems'),

        totalVatIncluded: function () {
            return this.get('priceTotal.vatIncluded');
        }.onChange('priceTotal'),

        totalVatExcluded: function() {
            return this.get('priceTotal.vatExcluded');
        }.onChange('priceTotal'),

        totalVat: function () {
            return Math.max(0, (this.totalVatIncluded() - this.totalVatExcluded()) || 0);
        }.onChange('priceTotal')

    });
});<|endoftext|>


<commit_before>function ICustomTag() {
	this.attr = {};
}

ICustomTag.prototype.render = function (values, stream) {
	return stream instanceof Array ? Builder.buildHtml(this.nodes, values, stream) : Builder.buildDom(this.nodes, values, stream);
};

var CustomTags = function () {

	var renderICustomTag = ICustomTag.prototype.render;

	function List() {
		this.attr = {};
	}

	List.prototype.render = function (values, container, cntx) {
		var attr = this.attr,
				attrTemplate = attr.template,
				value = Helper.getProperty(values, attr.value),
				nodes,
				template,
				fn,
				i, length;

		if (!(value instanceof Array))
			return container;

		if (attrTemplate != null) {
			template = document.querySelector(attrTemplate).innerHTML;
			this.nodes = nodes = mask.compile(template);
		}

		if (this.nodes == null)
			return container;

		//- fn = container instanceof Array ? 'buildHtml' : 'buildDom';
		fn = Builder[container.buffer != null ? 'buildHtml' : 'buildDom'];

		for (i = 0, length = value.length; i < length; i++) {
			fn.call(Builder, this.nodes, value[i], container, cntx);
		}

		return container;
	};

	function Visible() {
		this.attr = {}
	}

	Visible.prototype.render = function (values, container, cntx) {
		if (!ValueUtilities.out.isCondition(this.attr.check, values))
			return container;
		else
			return renderICustomTag.call(this, values, container, cntx);
	};

	function Binding() {
		this.attr = {}
	}

	Binding.prototype.render = function () {
		// lazy self definition

		var
				objectDefineProperty = Object.defineProperty,
				supportsDefineProperty = false,
				watchedObjects;

		// test for support
		if (objectDefineProperty) {
			try {
				supportsDefineProperty = Object.defineProperty({}, 'x', {get: function () {
					return true
				}}).x;
			}
			catch (e) {
				supportsDefineProperty = false;
			}
		}
		else {
			if (Object.prototype.__defineGetter__) {
				objectDefineProperty = function (obj, prop, desc) {
					if (hasOwnProperty.call(desc, 'get')) obj.__defineGetter__(prop, desc.get);
					if (hasOwnProperty.call(desc, 'set')) obj.__defineSetter__(prop, desc.set);
				};

				supportsDefineProperty = true;
			}
		}

		// defining polyfill
		if (!supportsDefineProperty) {
			watchedObjects = [];

			objectDefineProperty = function (obj, prop, desc) {
				var
						objectWrapper,
						found = false,
						i, length;

				for (i = 0, length = watchedObjects.length; i < length; i++) {
					objectWrapper = watchedObjects[i];
					if (objectWrapper.obj === obj) {
						found = true;
						break;
					}
				}

				if (!found)
					objectWrapper = watchedObjects[i] = {obj: obj, props: {}};

				objectWrapper.props[prop] = {
					value: obj[prop],
					set  : desc.set
				};
			};

			function ticker() {
				var
						objectWrapper,
						i, length,
						props,
						prop,
						propObj,
						newValue;

				for (i = 0, length = watchedObjects.length; i < length; i++) {
					objectWrapper = watchedObjects[i];
					props = objectWrapper.props;

					for (prop in props)
						if (hasOwnProperty.call(props, prop)) {
							propObj = props[prop];
							newValue = objectWrapper.obj[prop];
							if (newValue !== propObj.value)
								propObj.set.call(null, newValue);
						}
				}

				setTimeout(ticker, 16);
			}

			ticker();
		}

		return (Binding.prototype.render = function (values, container) {
			var
					attrValue = this.attr.value,
					value = values[attrValue];

			objectDefineProperty.call(Object, values, attrValue, {
				get: function () {
					return value;
				},
				set: function (x) {
					container.innerHTML = value = x;
				}
			});

			container.innerHTML = value;
			return container;
		}
				).apply(this, arguments);
	};

	return {
		all: {
			list   : List,
			visible: Visible,
			bind   : Binding
		}
	};

}();
<commit_msg>[fix] setter polyfill: ticker declaration -> definition<commit_after>function ICustomTag() {
	this.attr = {};
}

ICustomTag.prototype.render = function (values, stream) {
	return stream instanceof Array ? Builder.buildHtml(this.nodes, values, stream) : Builder.buildDom(this.nodes, values, stream);
};

var CustomTags = function () {

	var renderICustomTag = ICustomTag.prototype.render;

	function List() {
		this.attr = {};
	}

	List.prototype.render = function (values, container, cntx) {
		var attr = this.attr,
				attrTemplate = attr.template,
				value = Helper.getProperty(values, attr.value),
				nodes,
				template,
				fn,
				i, length;

		if (!(value instanceof Array))
			return container;

		if (attrTemplate != null) {
			template = document.querySelector(attrTemplate).innerHTML;
			this.nodes = nodes = mask.compile(template);
		}

		if (this.nodes == null)
			return container;

		//- fn = container instanceof Array ? 'buildHtml' : 'buildDom';
		fn = Builder[container.buffer != null ? 'buildHtml' : 'buildDom'];

		for (i = 0, length = value.length; i < length; i++) {
			fn.call(Builder, this.nodes, value[i], container, cntx);
		}

		return container;
	};

	function Visible() {
		this.attr = {}
	}

	Visible.prototype.render = function (values, container, cntx) {
		if (!ValueUtilities.out.isCondition(this.attr.check, values))
			return container;
		else
			return renderICustomTag.call(this, values, container, cntx);
	};

	function Binding() {
		this.attr = {}
	}

	Binding.prototype.render = function () {
		// lazy self definition

		var
				objectDefineProperty = Object.defineProperty,
				supportsDefineProperty = false,
				watchedObjects,
				ticker;

		// test for support
		if (objectDefineProperty) {
			try {
				supportsDefineProperty = Object.defineProperty({}, 'x', {get: function () {
					return true
				}}).x;
			}
			catch (e) {
				supportsDefineProperty = false;
			}
		}
		else {
			if (Object.prototype.__defineGetter__) {
				objectDefineProperty = function (obj, prop, desc) {
					if (hasOwnProperty.call(desc, 'get')) obj.__defineGetter__(prop, desc.get);
					if (hasOwnProperty.call(desc, 'set')) obj.__defineSetter__(prop, desc.set);
				};

				supportsDefineProperty = true;
			}
		}

		// defining polyfill
		if (!supportsDefineProperty) {
			watchedObjects = [];

			objectDefineProperty = function (obj, prop, desc) {
				var
						objectWrapper,
						found = false,
						i, length;

				for (i = 0, length = watchedObjects.length; i < length; i++) {
					objectWrapper = watchedObjects[i];
					if (objectWrapper.obj === obj) {
						found = true;
						break;
					}
				}

				if (!found)
					objectWrapper = watchedObjects[i] = {obj: obj, props: {}};

				objectWrapper.props[prop] = {
					value: obj[prop],
					set  : desc.set
				};
			};

			ticker = function () {
				var
						objectWrapper,
						i, length,
						props,
						prop,
						propObj,
						newValue;

				for (i = 0, length = watchedObjects.length; i < length; i++) {
					objectWrapper = watchedObjects[i];
					props = objectWrapper.props;

					for (prop in props)
						if (hasOwnProperty.call(props, prop)) {
							propObj = props[prop];
							newValue = objectWrapper.obj[prop];
							if (newValue !== propObj.value)
								propObj.set.call(null, newValue);
						}
				}

				setTimeout(ticker, 16);
			};

			ticker();
		}

		return (Binding.prototype.render = function (values, container) {
			var
					attrValue = this.attr.value,
					value = values[attrValue];

			objectDefineProperty.call(Object, values, attrValue, {
				get: function () {
					return value;
				},
				set: function (x) {
					container.innerHTML = value = x;
				}
			});

			container.innerHTML = value;
			return container;
		}
				).apply(this, arguments);
	};

	return {
		all: {
			list   : List,
			visible: Visible,
			bind   : Binding
		}
	};

}();
<|endoftext|>


<commit_before>import { update, rotate, zoom } from './orbitControls'

import most from 'most'
import { sample } from 'most'
import { fromEvent, combineArray, combine, mergeArray } from 'most'
import { interactionsFromEvents, pointerGestures } from '../interactions/pointerGestures'

import { model } from '../utils/modelUtils'
import animationFrames from '../utils/animationFrames'

export function controlsLoop (interactions, cameraData, fullData) {
  const {settings, camera} = cameraData
  const {gestures} = interactions

  //gestures.taps.taps$.forEach(e=>console.log('taps',e))
  //gestures.taps.shortSingleTaps$.forEach(e => console.log('shortSingleTaps', e))
  //gestures.taps.shortDoubleTaps$.forEach(e => console.log('shortDoubleTaps', e))
  //gestures.taps.longTaps$.forEach(e => console.log('longTaps', e))

  const rate$ = animationFrames() //heartBeat
  //const heartBeat$ = most.periodic(16, 'x')
  //sample(world, rate)

  const mobileReductor = 5.0 // how much we want to divide touch deltas to get movements on mobile

  const dragMoves$ = gestures.dragMoves
    //.throttle(16) // FIXME: not sure, could be optimized some more
    .filter(x => x !== undefined)
    .map( function(data) {
      let delta = [data.delta.x,data.delta.y]
      if(data.type === 'touch'){
        delta = delta.map(x=>x/mobileReductor)
      }
      return delta
    })
    .map(function (delta) {
      const angle = [Math.PI * delta[0], - Math.PI * delta[1]]
      return angle
    })

  const zooms$ = gestures.zooms
    .map(x => -x) // we invert zoom direction
    .startWith(0)
    .filter(x => !isNaN(x))

  function makeCameraModel () {
    function applyRotation (state, angles) {
      state = rotate(settings, state, angles) // mutating, meh
      state = update(settings, state) // not sure
      return state
    }

    function applyZoom (state, zooms) {
      console.log('applyZoom', zooms)
      state = zoom(settings, state, zooms) // mutating, meh
      state = update(settings, state) // not sure
      return state
    }

    function updateState (state) {
      return update(settings, state)
    }

    const updateFunctions = {applyZoom, applyRotation, updateState}
    const actions = {applyZoom: zooms$, applyRotation: dragMoves$, updateState: rate$}

    const cameraState$ = model(camera, actions, updateFunctions)

    return cameraState$
    return most.merge(
      cameraState$.take(2),
      cameraState$// .throttle(20)
    )
    // .map(cameraState => update(settings, cameraState))
  }

  function updateCompleteState (cameraState) {
    let data = fullData
    data.camera = cameraState
    return data
  }

  const cameraState$ = makeCameraModel()

  return cameraState$
    .sample(x=>x, rate$)
    .filter(x => x.changed)
    .merge(cameraState$)
    .map(updateCompleteState)
    //.tap(e=>console.log('here'))

   /*const updateForRender$ = most.sample(updateCompleteState, heartBeat$, cameraState$)
     .filter(x => x.changed)
     .map(updateCompleteState)
   return updateForRender$*/
}

/*const cameraState$ = most.combine(function (angles, zooms) {
  return {angles, zooms}
}, dragMoves$, zooms$)
  .scan(function (state, current) {
    const {angles, zooms} = current
    // console.log('delta', angles)

    let cameraState = update(settings, camera)
    cameraState = zoom(settings, cameraState, zooms) // mutating, meh
    cameraState = rotate(settings, cameraState, angles)
    cameraState = update(settings, cameraState)

    let data = fullData
    data.camera = cameraState
    return data
  }, undefined)
  .filter(x => x !== undefined)*/

export function controlsLoopOld (cameraDefaults, render, fullData) {
  const {settings, camera} = cameraDefaults
  // FIXME: hack for now
  let cameraState = update(settings, camera)
  let prevMouse = [0, 0]

  function onMouseChange (buttons, x, y, mods) {
    // console.log('mouse-change', buttons, x, y, mods)
    if (buttons === 1) {
      let delta = [x - prevMouse[0], y - prevMouse[1]]
      let angle = [0, 0]
      angle[0] = 2 * Math.PI * delta[0] / 1800 * 2.0
      angle[1] = -2 * Math.PI * delta[1] / 1800 * 2.0

      cameraState = rotate(settings, cameraState, angle)
    }
    prevMouse = [x, y]
  }

  function onMouseWheel (dx, dy) {
    const zoomDelta = dy
    cameraState = zoom(settings, cameraState, zoomDelta)
  }

  function updateStep () {
    cameraState = update(settings, cameraState)

    if (cameraState && cameraState.changed) {
      let data = fullData
      data.camera = cameraState
      render(data)
    }
    window.requestAnimationFrame(updateStep)
  }

  require('mouse-change')(onMouseChange)
  require('mouse-wheel')(onMouseWheel)

  let data = fullData
  data.camera = cameraState
  render(data)

  requestAnimationFrame(updateStep)
}
<commit_msg>refactor(controlsLoop): cleaned up controlsLoop module<commit_after>import { update, rotate, zoom } from './orbitControls'

import most from 'most'
import { sample } from 'most'
import { fromEvent, combineArray, combine, mergeArray } from 'most'
import { interactionsFromEvents, pointerGestures } from '../interactions/pointerGestures'

import { model } from '../utils/modelUtils'
import animationFrames from '../utils/animationFrames'

export function controlsLoop (interactions, cameraData, fullData) {
  const {settings, camera} = cameraData
  const {gestures} = interactions

  const rate$ = animationFrames() //heartBeat
  //const heartBeat$ = most.periodic(16, 'x')
  //sample(world, rate)

  const mobileReductor = 5.0 // how much we want to divide touch deltas to get movements on mobile

  const dragMoves$ = gestures.dragMoves
    //.throttle(16) // FIXME: not sure, could be optimized some more
    .filter(x => x !== undefined)
    .map( function(data) {
      let delta = [data.delta.x,data.delta.y]
      if(data.type === 'touch'){
        delta = delta.map(x=>x/mobileReductor)
      }
      return delta
    })
    .map(function (delta) {
      const angle = [Math.PI * delta[0], - Math.PI * delta[1]]
      return angle
    })

  const zooms$ = gestures.zooms
    .map(x => -x) // we invert zoom direction
    .startWith(0)
    .filter(x => !isNaN(x))

  function makeCameraModel () {
    function applyRotation (state, angles) {
      state = rotate(settings, state, angles) // mutating, meh
      state = update(settings, state) // not sure
      return state
    }

    function applyZoom (state, zooms) {
      console.log('applyZoom', zooms)
      state = zoom(settings, state, zooms) // mutating, meh
      state = update(settings, state) // not sure
      return state
    }

    function updateState (state) {
      return update(settings, state)
    }

    const updateFunctions = {applyZoom, applyRotation, updateState}
    const actions = {applyZoom: zooms$, applyRotation: dragMoves$, updateState: rate$}

    const cameraState$ = model(camera, actions, updateFunctions)

    return cameraState$
    return most.merge(
      cameraState$.take(2),
      cameraState$
    )
  }

  function updateCompleteState (cameraState) {
    let data = fullData
    data.camera = cameraState
    return data
  }

  const cameraState$ = makeCameraModel()

  return cameraState$
    .sample(x=>x, rate$)
    .filter(x => x.changed)
    .merge(cameraState$)
    .map(updateCompleteState)
    //.tap(e=>console.log('here'))
   /*const updateForRender$ = most.sample(updateCompleteState, heartBeat$, cameraState$)
     .filter(x => x.changed)
     .map(updateCompleteState)
   return updateForRender$*/
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';
import { sortComments } from '../../helpers/sortHelpers';
import Loading from '../Icon/Loading';
import CommentForm from './CommentForm';
import Comment from './Comment';
import './Comments.less';

class Comments extends React.Component {
  static propTypes = {
    authenticated: PropTypes.bool.isRequired,
    username: PropTypes.string,
    parentPost: PropTypes.shape(),
    comments: PropTypes.arrayOf(PropTypes.shape()),
    commentsChildren: PropTypes.shape(),
    pendingVotes: PropTypes.arrayOf(PropTypes.shape({
      id: PropTypes.number,
      percent: PropTypes.number,
    })),
    loading: PropTypes.bool,
    show: PropTypes.bool,
    onLikeClick: PropTypes.func,
    onDislikeClick: PropTypes.func,
    onSendComment: PropTypes.func,
  };

  static defaultProps = {
    username: undefined,
    parentPost: undefined,
    comments: [],
    commentsChildren: undefined,
    pendingVotes: [],
    loading: false,
    show: false,
    onLikeClick: () => {},
    onDislikeClick: () => {},
    onSendComment: () => {},
  };

  constructor(props) {
    super(props);
    this.state = {
      sort: 'BEST',
      showCommentFormLoading: false,
      commentFormText: '',
    };
  }

  handleSortClick = (e) => {
    const type = e.target.dataset.type;
    if (type) {
      this.setState({
        sort: type,
      });
    }
  };

  handleImageInserted = (blob, callback, errorCallback) => {
    const { authenticated, username } = this.props;
    if (!authenticated) return;

    const formData = new FormData();
    formData.append('files', blob);

    fetch(`https://busy-img.herokuapp.com/@${username}/uploads`, {
      method: 'POST',
      body: formData,
    })
      .then(res => res.json())
      .then(res => callback(res.secure_url, blob.name))
      .catch(() => errorCallback());
  };

  submitComment = (parentPost, commentValue) => {
    this.setState({ showCommentFormLoading: true });
    this.props
      .onSendComment(parentPost, commentValue)
      .then(() => {
        this.setState({ showCommentFormLoading: false, commentFormText: '' });
      })
      .catch(() => {
        this.setState({
          showCommentFormLoading: false,
          commentFormText: commentValue,
        });
      });
  };

  render() {
    const {
      comments,
      loading,
      show,
      commentsChildren,
      pendingVotes,
      onLikeClick,
      onDislikeClick,
      authenticated,
      username,
    } = this.props;
    const { sort } = this.state;

    return (

            <FormattedMessage id="comments" defaultMessage="Comments" />

              <FormattedMessage id="sort_best" defaultMessage="Best" />

              <FormattedMessage id="sort_newest" defaultMessage="Newest" />

              <FormattedMessage id="sort_oldest" defaultMessage="Oldest" />

        {authenticated &&
          <CommentForm
            parentPost={this.props.parentPost}
            username={username}
            onSubmit={this.submitComment}
            isLoading={this.state.showCommentFormLoading}
            inputValue={this.state.commentFormText}
            onImageInserted={this.handleImageInserted}
          />}
        {loading && <Loading />}
        {!loading &&
          show &&
          comments &&
          sortComments(comments, sort).map(comment =>
            (<Comment
              key={comment.id}
              authenticated={authenticated}
              username={username}
              comment={comment}
              sort={sort}
              rootPostAuthor={this.props.parentPost && this.props.parentPost.author}
              commentsChildren={commentsChildren}
              pendingVotes={pendingVotes}
              onLikeClick={onLikeClick}
              onDislikeClick={onDislikeClick}
              onSendComment={this.props.onSendComment}
            />),
          )}

    );
  }
}

export default Comments;
<commit_msg>Fix handling sort click<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { FormattedMessage } from 'react-intl';
import { sortComments } from '../../helpers/sortHelpers';
import Loading from '../Icon/Loading';
import CommentForm from './CommentForm';
import Comment from './Comment';
import './Comments.less';

class Comments extends React.Component {
  static propTypes = {
    authenticated: PropTypes.bool.isRequired,
    username: PropTypes.string,
    parentPost: PropTypes.shape(),
    comments: PropTypes.arrayOf(PropTypes.shape()),
    commentsChildren: PropTypes.shape(),
    pendingVotes: PropTypes.arrayOf(PropTypes.shape({
      id: PropTypes.number,
      percent: PropTypes.number,
    })),
    loading: PropTypes.bool,
    show: PropTypes.bool,
    onLikeClick: PropTypes.func,
    onDislikeClick: PropTypes.func,
    onSendComment: PropTypes.func,
  };

  static defaultProps = {
    username: undefined,
    parentPost: undefined,
    comments: [],
    commentsChildren: undefined,
    pendingVotes: [],
    loading: false,
    show: false,
    onLikeClick: () => {},
    onDislikeClick: () => {},
    onSendComment: () => {},
  };

  constructor(props) {
    super(props);
    this.state = {
      sort: 'BEST',
      showCommentFormLoading: false,
      commentFormText: '',
    };
  }

  handleSortClick = (e) => {
    const type = e.target.parentNode && e.target.parentNode.dataset.type;
    if (type) {
      this.setState({
        sort: type,
      });
    }
  };

  handleImageInserted = (blob, callback, errorCallback) => {
    const { authenticated, username } = this.props;
    if (!authenticated) return;

    const formData = new FormData();
    formData.append('files', blob);

    fetch(`https://busy-img.herokuapp.com/@${username}/uploads`, {
      method: 'POST',
      body: formData,
    })
      .then(res => res.json())
      .then(res => callback(res.secure_url, blob.name))
      .catch(() => errorCallback());
  };

  submitComment = (parentPost, commentValue) => {
    this.setState({ showCommentFormLoading: true });
    this.props
      .onSendComment(parentPost, commentValue)
      .then(() => {
        this.setState({ showCommentFormLoading: false, commentFormText: '' });
      })
      .catch(() => {
        this.setState({
          showCommentFormLoading: false,
          commentFormText: commentValue,
        });
      });
  };

  render() {
    const {
      comments,
      loading,
      show,
      commentsChildren,
      pendingVotes,
      onLikeClick,
      onDislikeClick,
      authenticated,
      username,
    } = this.props;
    const { sort } = this.state;

    return (

            <FormattedMessage id="comments" defaultMessage="Comments" />

              <FormattedMessage id="sort_best" defaultMessage="Best" />

              <FormattedMessage id="sort_newest" defaultMessage="Newest" />

              <FormattedMessage id="sort_oldest" defaultMessage="Oldest" />

        {authenticated &&
          <CommentForm
            parentPost={this.props.parentPost}
            username={username}
            onSubmit={this.submitComment}
            isLoading={this.state.showCommentFormLoading}
            inputValue={this.state.commentFormText}
            onImageInserted={this.handleImageInserted}
          />}
        {loading && <Loading />}
        {!loading &&
          show &&
          comments &&
          sortComments(comments, sort).map(comment =>
            (<Comment
              key={comment.id}
              authenticated={authenticated}
              username={username}
              comment={comment}
              sort={sort}
              rootPostAuthor={this.props.parentPost && this.props.parentPost.author}
              commentsChildren={commentsChildren}
              pendingVotes={pendingVotes}
              onLikeClick={onLikeClick}
              onDislikeClick={onDislikeClick}
              onSendComment={this.props.onSendComment}
            />),
          )}

    );
  }
}

export default Comments;
<|endoftext|>


<commit_before>import app from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'

const config = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_FIREBASE_APP_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
}

class Firebase {
  constructor() {
    // Initialize the app
    app.initializeApp(config)
    // Get email auth provider
    this.emailAuthProvider = app.auth.EmailAuthProvider
    // Get FieldValue
    this.FieldValue = app.firestore.FieldValue
    // Initialize auth
    this.auth = app.auth()
    // Initialize firestore
    this.store = app.firestore()
    // Initialize social login providers
    this.googleProvider = new app.auth.GoogleAuthProvider()
    this.facebookProvider = new app.auth.FacebookAuthProvider()
  }

  // *** Auth API ***
  doCreateUserWithEmailAndPassword = (email, password) =>
    this.auth.createUserWithEmailAndPassword(email, password)

  doSignInWithEmailAndPassword = (email, password) =>
    this.auth.signInWithEmailAndPassword(email, password)

  doSignInWithGoogle = () => this.auth.signInWithPopup(this.googleProvider)

  doSignInWithFacebook = () => this.auth.signInWithPopup(this.facebookProvider)

  doSignOut = () => this.auth.signOut()

  doPasswordReset = email => this.auth.sendPasswordResetEmail(email)

  doPasswordUpdate = password => this.auth.currentUser.updatePassword(password)

  doSendEmailVerification = () =>
    this.auth.currentUser.sendEmailVerification({
      url: process.env.REACT_APP_CONFIRMATION_EMAIL_REDIRECT,
    })

  // *** Auth State API ***
  onAuthUserListener = (next, fallback) =>
    this.auth.onAuthStateChanged(authUser => {
      if (authUser) {
        this.user(authUser.uid)
          .get()
          .then(snapshot => {
            // Get user document from snapshot data
            const user = snapshot.data()
            // Default empty roles
            if (!user.roles) {
              user.roles = {}
            }
            // Merge auth and user document
            authUser = {
              uid: authUser.uid,
              email: authUser.email,
              emailVerified: authUser.emailVerified,
              providerData: authUser.providerData,
              ...user,
            }
            next(authUser)
          })
      } else {
        fallback()
      }
    })

  // *** User API ***
  users = () => this.store.collection('users')

  user = id => this.users().doc(id)

  userFriends = id => this.user(id).collection('friends')

  findUserForEmail = email => this.users().where('email', '==', email)

  findUserFriendForEmail = (email, id) =>
    this.userFriends(id).where('email', '==', email)

  userGames = id => this.user(id).collection('games')

  // *** Qwest API ***
  qwests = () => this.store.collection('qwests')

  qwest = id => this.qwests().doc(id)

  gameQwests = id =>
    this.qwests()
      .where('gameId', '==', id)
      .where('isCompleted', '==', false)

  gameCompletedQwests = id =>
    this.qwests()
      .where('gameId', '==', id)
      .where('isCompleted', '==', true)

  userQwests = id =>
    this.qwests()
      .where('userId', '==', id)
      .where('isCompleted', '==', false)

  userCompletedQwests = id =>
    this.qwests()
      .where('userId', '==', id)
      .where('isCompleted', '==', true)

  createQwest = qwest => {
    this.qwests().add(qwest)
  }

  updateQwest = (qwest, updatedQwest) => {
    this.qwest(qwest.id).update(updatedQwest)
  }

  completeQwest = id => {
    // Complete qwest
    this.qwest(id).update({
      isCompleted: true,
    })
  }

  resetQwest = id => {
    // Reset qwest
    this.qwest(id).update({
      isCompleted: false,
    })
  }

  deleteQwest = async id => {
    // Get qwests's tasks collections
    const tasks = await this.qwestTasks(id).get()
    // Iterate through each task
    tasks.docs.forEach(task => {
      // Delete task
      task.ref.delete()
    })
    // Delete qwest
    this.qwest(id).delete()
  }

  // *** Game API ***
  games = () => this.store.collection('games')

  game = id => this.games().doc(id)

  gamePlayers = id => this.game(id).collection('players')

  gameSubCollections = id =>
    this.store.collectionGroup('games').where('gameId', '==', id)

  createGame = async (game, authUser) => {
    // Create the new game
    const newGame = await this.games().add(game)
    // Create players collection for game
    newGame
      .collection('players')
      .doc(authUser.uid)
      .set({
        userId: authUser.uid,
        username: authUser.username,
        email: authUser.email,
      })
    // Add game to user's games collection
    this.userGames(authUser.uid)
      .doc(newGame.id)
      .set({
        gameId: newGame.id,
        name: game.name,
      })
  }

  updateGame = (game, updatedGame) => {
    // Update the game
    this.game(game.id).update(updatedGame)
    // Update game sub-collections
    this.gameSubCollections(game.id)
      .get()
      .then(snapshot => {
        snapshot.docs.forEach(doc => {
          doc.ref.update(updatedGame)
        })
      })
  }

  deleteGame = async id => {
    // Get game's players collections
    const players = await this.gamePlayers(id).get()
    // Iterate through each player
    players.docs.forEach(player => {
      // Remove game from player's games collection
      this.userGames(player.id)
        .doc(id)
        .delete()
    })
    // Get game's qwests collections
    const qwests = await this.gameQwests(id).get()
    // Iterate through each qwest
    qwests.docs.forEach(qwest => {
      // Delete qwest
      this.deleteQwest(qwest.id)
    })
    // Delete game
    this.game(id).delete()
  }

  // *** Invite API ***
  invites = () => this.store.collection('invites')

  invite = id => this.invites().doc(id)

  findSentInvitesForUser = (userId, authUserId) =>
    this.invites()
      .where('requestedId', '==', userId)
      .where('requesterId', '==', authUserId)
      .where('gameId', '==', null)

  findReceivedInvitesForUser = (userId, authUserId) =>
    this.invites()
      .where('requesterId', '==', userId)
      .where('requestedId', '==', authUserId)
      .where('gameId', '==', null)

  findSentGameInvitesForUser = (userId, gameId) =>
    this.invites()
      .where('requestedId', '==', userId)
      .where('gameId', '==', gameId)

  sentUserInvites = id =>
    this.invites()
      .where('requesterId', '==', id)
      .where('gameId', '==', null)

  sentGameInvites = id => this.invites().where('gameId', '==', id)

  receivedUserInvites = id =>
    this.invites()
      .where('requestedId', '==', id)
      .where('gameId', '==', null)

  gameInvitesForUser = id =>
    this.invites()
      .where('requestedId', '==', id)
      .where('gameId', '>=', '')

  createInvite = invite => {
    this.invites().add(invite)
  }

  acceptFriendInvite = invite => {
    // Update friends document of requested user with requester user data
    this.userFriends(invite.data().requestedId)
      .doc(invite.data().requesterId)
      .set({
        userId: invite.data().requesterId,
        username: invite.data().requesterUsername,
        email: invite.data().requesterEmail,
      })
    // Update friends document of requester user with requested user data
    this.userFriends(invite.data().requesterId)
      .doc(invite.data().requestedId)
      .set({
        userId: invite.data().requestedId,
        username: invite.data().requestedUsername,
        email: invite.data().requestedEmail,
      })
  }

  acceptGameInvite = invite => {
    // Update games document's players collection with requested user data
    this.gamePlayers(invite.data().gameId)
      .doc(invite.data().requestedId)
      .set({
        userId: invite.data().requestedId,
        username: invite.data().requestedUsername,
        email: invite.data().requestedEmail,
      })
    // Update user document's games collection with game data
    this.userGames(invite.data().requestedId)
      .doc(invite.data().gameId)
      .set({
        gameId: invite.data().gameId,
        name: invite.data().gameName,
      })
  }

  // *** Post API ***
  qwestPosts = id => this.qwest(id).collection('posts')

  gamePosts = id => this.game(id).collection('posts')

  createQwestPost = (id, post) => this.qwestPosts(id).add(post)

  createGamePost = (id, post) => this.gamePosts(id).add(post)

  mostRecentQwestPosts = id => this.qwestPosts(id).orderBy('createdAt', 'desc')

  mostRecentGamePosts = id => this.gamePosts(id).orderBy('createdAt', 'desc')

  // *** Task API ***
  tasks = () => this.store.collection('tasks')

  task = id => this.tasks().doc(id)

  qwestTasks = id => this.tasks().where('qwestId', '==', id)

  qwestActiveTasks = id =>
    this.tasks()
      .where('qwestId', '==', id)
      .where('isCompleted', '==', false)

  qwestCompletedTasks = id =>
    this.tasks()
      .where('qwestId', '==', id)
      .where('isCompleted', '==', true)

  createTask = task => this.tasks().add(task)

  updateTask = (task, updatedTask) => {
    this.task(task.id).update(updatedTask)
  }

  completeTask = id => {
    // Complete task
    this.task(id).update({
      isCompleted: true,
    })
  }

  resetTask = id => {
    // Reset task
    this.task(id).update({
      isCompleted: false,
    })
  }

  deleteTask = id => {
    // Delete task
    this.task(id).delete()
  }
}

export default Firebase
<commit_msg>Updated deleteQwest firebase API<commit_after>import app from 'firebase/app'
import 'firebase/auth'
import 'firebase/firestore'

const config = {
  apiKey: process.env.REACT_APP_FIREBASE_API_KEY,
  authDomain: process.env.REACT_APP_FIREBASE_AUTH_DOMAIN,
  databaseURL: process.env.REACT_FIREBASE_APP_DATABASE_URL,
  projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID,
  storageBucket: process.env.REACT_APP_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.REACT_APP_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_FIREBASE_APP_ID,
}

class Firebase {
  constructor() {
    // Initialize the app
    app.initializeApp(config)
    // Get email auth provider
    this.emailAuthProvider = app.auth.EmailAuthProvider
    // Get FieldValue
    this.FieldValue = app.firestore.FieldValue
    // Initialize auth
    this.auth = app.auth()
    // Initialize firestore
    this.store = app.firestore()
    // Initialize social login providers
    this.googleProvider = new app.auth.GoogleAuthProvider()
    this.facebookProvider = new app.auth.FacebookAuthProvider()
  }

  // *** Auth API ***
  doCreateUserWithEmailAndPassword = (email, password) =>
    this.auth.createUserWithEmailAndPassword(email, password)

  doSignInWithEmailAndPassword = (email, password) =>
    this.auth.signInWithEmailAndPassword(email, password)

  doSignInWithGoogle = () => this.auth.signInWithPopup(this.googleProvider)

  doSignInWithFacebook = () => this.auth.signInWithPopup(this.facebookProvider)

  doSignOut = () => this.auth.signOut()

  doPasswordReset = email => this.auth.sendPasswordResetEmail(email)

  doPasswordUpdate = password => this.auth.currentUser.updatePassword(password)

  doSendEmailVerification = () =>
    this.auth.currentUser.sendEmailVerification({
      url: process.env.REACT_APP_CONFIRMATION_EMAIL_REDIRECT,
    })

  // *** Auth State API ***
  onAuthUserListener = (next, fallback) =>
    this.auth.onAuthStateChanged(authUser => {
      if (authUser) {
        this.user(authUser.uid)
          .get()
          .then(snapshot => {
            // Get user document from snapshot data
            const user = snapshot.data()
            // Default empty roles
            if (!user.roles) {
              user.roles = {}
            }
            // Merge auth and user document
            authUser = {
              uid: authUser.uid,
              email: authUser.email,
              emailVerified: authUser.emailVerified,
              providerData: authUser.providerData,
              ...user,
            }
            next(authUser)
          })
      } else {
        fallback()
      }
    })

  // *** User API ***
  users = () => this.store.collection('users')

  user = id => this.users().doc(id)

  userFriends = id => this.user(id).collection('friends')

  findUserForEmail = email => this.users().where('email', '==', email)

  findUserFriendForEmail = (email, id) =>
    this.userFriends(id).where('email', '==', email)

  userGames = id => this.user(id).collection('games')

  // *** Qwest API ***
  qwests = () => this.store.collection('qwests')

  qwest = id => this.qwests().doc(id)

  gameQwests = id =>
    this.qwests()
      .where('gameId', '==', id)
      .where('isCompleted', '==', false)

  gameCompletedQwests = id =>
    this.qwests()
      .where('gameId', '==', id)
      .where('isCompleted', '==', true)

  userQwests = id =>
    this.qwests()
      .where('userId', '==', id)
      .where('isCompleted', '==', false)

  userCompletedQwests = id =>
    this.qwests()
      .where('userId', '==', id)
      .where('isCompleted', '==', true)

  createQwest = qwest => {
    this.qwests().add(qwest)
  }

  updateQwest = (qwest, updatedQwest) => {
    this.qwest(qwest.id).update(updatedQwest)
  }

  completeQwest = id => {
    // Complete qwest
    this.qwest(id).update({
      isCompleted: true,
    })
  }

  resetQwest = id => {
    // Reset qwest
    this.qwest(id).update({
      isCompleted: false,
    })
  }

  deleteQwest = async id => {
    // Get qwests's tasks collections
    const tasks = await this.qwestTasks(id).get()
    // Iterate through each task
    tasks.docs.forEach(task => {
      // Delete task
      task.ref.delete()
    })
    // Get qwests's posts collections
    const posts = await this.qwestPosts(id).get()
    // Iterate through each post
    posts.docs.forEach(post => {
      // Delete post
      post.ref.delete()
    })
    // Delete qwest
    this.qwest(id).delete()
  }

  // *** Game API ***
  games = () => this.store.collection('games')

  game = id => this.games().doc(id)

  gamePlayers = id => this.game(id).collection('players')

  gameSubCollections = id =>
    this.store.collectionGroup('games').where('gameId', '==', id)

  createGame = async (game, authUser) => {
    // Create the new game
    const newGame = await this.games().add(game)
    // Create players collection for game
    newGame
      .collection('players')
      .doc(authUser.uid)
      .set({
        userId: authUser.uid,
        username: authUser.username,
        email: authUser.email,
      })
    // Add game to user's games collection
    this.userGames(authUser.uid)
      .doc(newGame.id)
      .set({
        gameId: newGame.id,
        name: game.name,
      })
  }

  updateGame = (game, updatedGame) => {
    // Update the game
    this.game(game.id).update(updatedGame)
    // Update game sub-collections
    this.gameSubCollections(game.id)
      .get()
      .then(snapshot => {
        snapshot.docs.forEach(doc => {
          doc.ref.update(updatedGame)
        })
      })
  }

  deleteGame = async id => {
    // Get game's players collections
    const players = await this.gamePlayers(id).get()
    // Iterate through each player
    players.docs.forEach(player => {
      // Remove game from player's games collection
      this.userGames(player.id)
        .doc(id)
        .delete()
    })
    // Get game's qwests collections
    const qwests = await this.gameQwests(id).get()
    // Iterate through each qwest
    qwests.docs.forEach(qwest => {
      // Delete qwest
      this.deleteQwest(qwest.id)
    })
    // Delete game
    this.game(id).delete()
  }

  // *** Invite API ***
  invites = () => this.store.collection('invites')

  invite = id => this.invites().doc(id)

  findSentInvitesForUser = (userId, authUserId) =>
    this.invites()
      .where('requestedId', '==', userId)
      .where('requesterId', '==', authUserId)
      .where('gameId', '==', null)

  findReceivedInvitesForUser = (userId, authUserId) =>
    this.invites()
      .where('requesterId', '==', userId)
      .where('requestedId', '==', authUserId)
      .where('gameId', '==', null)

  findSentGameInvitesForUser = (userId, gameId) =>
    this.invites()
      .where('requestedId', '==', userId)
      .where('gameId', '==', gameId)

  sentUserInvites = id =>
    this.invites()
      .where('requesterId', '==', id)
      .where('gameId', '==', null)

  sentGameInvites = id => this.invites().where('gameId', '==', id)

  receivedUserInvites = id =>
    this.invites()
      .where('requestedId', '==', id)
      .where('gameId', '==', null)

  gameInvitesForUser = id =>
    this.invites()
      .where('requestedId', '==', id)
      .where('gameId', '>=', '')

  createInvite = invite => {
    this.invites().add(invite)
  }

  acceptFriendInvite = invite => {
    // Update friends document of requested user with requester user data
    this.userFriends(invite.data().requestedId)
      .doc(invite.data().requesterId)
      .set({
        userId: invite.data().requesterId,
        username: invite.data().requesterUsername,
        email: invite.data().requesterEmail,
      })
    // Update friends document of requester user with requested user data
    this.userFriends(invite.data().requesterId)
      .doc(invite.data().requestedId)
      .set({
        userId: invite.data().requestedId,
        username: invite.data().requestedUsername,
        email: invite.data().requestedEmail,
      })
  }

  acceptGameInvite = invite => {
    // Update games document's players collection with requested user data
    this.gamePlayers(invite.data().gameId)
      .doc(invite.data().requestedId)
      .set({
        userId: invite.data().requestedId,
        username: invite.data().requestedUsername,
        email: invite.data().requestedEmail,
      })
    // Update user document's games collection with game data
    this.userGames(invite.data().requestedId)
      .doc(invite.data().gameId)
      .set({
        gameId: invite.data().gameId,
        name: invite.data().gameName,
      })
  }

  // *** Post API ***
  qwestPosts = id => this.qwest(id).collection('posts')

  gamePosts = id => this.game(id).collection('posts')

  createQwestPost = (id, post) => this.qwestPosts(id).add(post)

  createGamePost = (id, post) => this.gamePosts(id).add(post)

  mostRecentQwestPosts = id => this.qwestPosts(id).orderBy('createdAt', 'desc')

  mostRecentGamePosts = id => this.gamePosts(id).orderBy('createdAt', 'desc')

  // *** Task API ***
  tasks = () => this.store.collection('tasks')

  task = id => this.tasks().doc(id)

  qwestTasks = id => this.tasks().where('qwestId', '==', id)

  qwestActiveTasks = id =>
    this.tasks()
      .where('qwestId', '==', id)
      .where('isCompleted', '==', false)

  qwestCompletedTasks = id =>
    this.tasks()
      .where('qwestId', '==', id)
      .where('isCompleted', '==', true)

  createTask = task => this.tasks().add(task)

  updateTask = (task, updatedTask) => {
    this.task(task.id).update(updatedTask)
  }

  completeTask = id => {
    // Complete task
    this.task(id).update({
      isCompleted: true,
    })
  }

  resetTask = id => {
    // Reset task
    this.task(id).update({
      isCompleted: false,
    })
  }

  deleteTask = id => {
    // Delete task
    this.task(id).delete()
  }
}

export default Firebase
<|endoftext|>


<commit_before>import './forms.scss';

import React, { Component, PropTypes } from 'react';
import { Field, reduxForm } from 'redux-form';
import { Form, Input, FormGroup, Col, Label, Button, ButtonGroup, Alert} from 'reactstrap';
import Spinner from '../Spinner';

import { required, email, minLength, url } from './validate';
import renderField from './renderField';

class NewLinkForm extends Component {

  renderError(error) {
    return (
      <Alert color="danger">
        { error }
      </Alert>
    );
  }

  render() {

    const { handleSubmit,
            pristine,
            reset,
            submitting,
            error,
            submitFailed,
            submitSucceeded,
            hideSubmit
        } = this.props;

    return (
      <Form onSubmit={handleSubmit}>
        <Field
          icon="info"
          name="title"
          type="text"
          label="Title"
          component={renderField}
          validate={[ required, minLength(3) ]}
        />
        <Field
          icon="link"
          name="link"
          type="text"
          label="Link"
          component={renderField}
          validate={[ required, url ]}
        />

        <Field
          icon="pencil-square-o"
          name="description"
          type="textarea"
          label="Description"
          component={renderField}
          validate={[ required ]}
        />

        {submitting && (
          <Spinner />
        )}

        {!submitting && submitFailed && (
          <Alert color="danger">
            Error { this.props.error }
          </Alert>
        )}

        {!submitting && submitSucceeded && (
          <Alert color="success">
            New Link Posted
          </Alert>
        )}

        {!hideSubmit && (
          <ButtonGroup>
            <Button type="submit" color="primary" disabled={submitting}>Post</Button>
          </ButtonGroup>
        )}

      </Form>
    );
  }
}

export default reduxForm({
  form: 'newLinkForm'
})(NewLinkForm);
<commit_msg>Dont show general error alert if theres no general error<commit_after>import './forms.scss';

import React, { Component, PropTypes } from 'react';
import { Field, reduxForm } from 'redux-form';
import { Form, Input, FormGroup, Col, Label, Button, ButtonGroup, Alert} from 'reactstrap';
import Spinner from '../Spinner';

import { required, email, minLength, url } from './validate';
import renderField from './renderField';

class NewLinkForm extends Component {

  renderError(error) {
    return (
      <Alert color="danger">
        { error }
      </Alert>
    );
  }

  render() {

    const { handleSubmit,
            pristine,
            reset,
            submitting,
            error,
            submitFailed,
            submitSucceeded,
            hideSubmit
        } = this.props;

    return (
      <Form onSubmit={handleSubmit}>
        <Field
          icon="info"
          name="title"
          type="text"
          label="Title"
          component={renderField}
          validate={[ required, minLength(3) ]}
        />
        <Field
          icon="link"
          name="link"
          type="text"
          label="Link"
          component={renderField}
          validate={[ required, url ]}
        />

        <Field
          icon="pencil-square-o"
          name="description"
          type="textarea"
          label="Description"
          component={renderField}
          validate={[ required ]}
        />

        {submitting && (
          <Spinner />
        )}

        {error && !submitting && submitFailed && (
          <Alert color="danger">
            Error { error }
          </Alert>
        )}

        {!submitting && submitSucceeded && (
          <Alert color="success">
            New Link Posted
          </Alert>
        )}

        {!hideSubmit && (
          <ButtonGroup>
            <Button type="submit" color="primary" disabled={submitting}>Post</Button>
          </ButtonGroup>
        )}

      </Form>
    );
  }
}

export default reduxForm({
  form: 'newLinkForm'
})(NewLinkForm);
<|endoftext|>


<commit_before>import React from 'react'
// import classes from './Counter.scss'
import ListItem from '../PostListItem';

export default class RedditPostList extends React.Component {
  componentDidMount() {
    const { fetchPosts } = this.props;

    fetchPosts();
  }

  render() {
    const { posts } = this.props;

    return (

        {posts.map( (post, idx) => (
          <ListItem
            key={idx}
            post={post} />
        ))}

    )
  }
}
<commit_msg>naively prevent refetching of images<commit_after>import React from 'react'
// import classes from './Counter.scss'
import ListItem from '../PostListItem';

export default class RedditPostList extends React.Component {
  componentDidMount() {
    const { posts, fetchPosts } = this.props;

    if (posts.length < 1) {
      fetchPosts();
    }
  }

  render() {
    const { posts } = this.props;

    return (

        {posts.map( (post, idx) => (
          <ListItem
            key={idx}
            post={post} />
        ))}

    )
  }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import _ from 'lodash';

import Card from './Card';

import './ShufflingCardGrid.css';

/*
  Card objects should be:
  {
    key: UUID,
    text: string
    accepted: boolean
    rejected: boolean -- should have?
  }
*/

export default class ShufflingCardGrid extends Component {

  constructor(props, context) {
    super(props, context);
    this.state = {
      centered: false,
      cardIndex: _.fromPairs(
        this.props.cards.map((c, index) => [c.key, index])
      )
    };
    // Just for demo purposes
    window.addEventListener('click', this.shuffle);
  }

  shuffle = () => {
    let n = this.props.cards.length;
    let newCards = this.props.cards.slice();
    for (let idx of _.range(n)) {
      let swapIdx = _.random(idx, n - 1);
      let [a, b] = [newCards[idx], newCards[swapIdx]];
      newCards[idx] = b;
      newCards[swapIdx] = a;
    }
    this.setState({
      // centered: true,
      cardIndex: _.fromPairs(
        newCards.map((c, index) => [c.key, index])
      )
    });
    setTimeout(() => this.setState({ centered: false }), 400);
  }

  render() {
    const rowSize = Math.floor(this.props.width / this.props.itemWidth);

    const indexToXPos = index => {
      const col = index % rowSize;
      const padding = (col + 1) * this.props.padding;
      return (col * this.props.itemWidth) + padding;
    }
    const indexToYPos = index => {
      const row = Math.floor(index / rowSize);
      const padding = (row + 1) * this.props.padding;
      return (row * this.props.itemHeight) + padding;
    }
    const transitionTime = index => {
      return 400 + (index % 6 * 50);
    }

    const actualWidth = indexToXPos(rowSize - 1) + this.props.padding + this.props.itemWidth;

    const centeredX = (this.props.width - this.props.itemWidth) / 2;
    const centeredY = (this.props.height - this.props.itemHeight) / 2;

    return (
      <div
        style={{ width: actualWidth }}
        className="ShufflingCardGrid"
        >
        {
          this.props.cards.map((_, index) => (
            <div
              className="Card fake"
              style={{
                top: indexToYPos(index),
                left: indexToXPos(index),
                width: this.props.itemWidth,
                height: this.props.itemHeight
              }}
            />
          ))
        }
        {
          this.props.cards.map((card, cardIndex) => {
            let index = this.state.cardIndex[card.key];
            return (
              <Card
                {...card}
                style={{
                  top: this.state.centered ? centeredY : indexToYPos(index),
                  left: this.state.centered ? centeredX : indexToXPos(index),
                  width: this.props.itemWidth,
                  height: this.props.itemHeight,
                  transition: `all ${transitionTime(cardIndex)}ms ease-in-out, box-shadow 80ms linear, background-color 80ms linear`
                }}
                onClick={(e) => {
                  e.stopPropagation();
                  this.props.onCardMark(card.key, 'selected');
                }}
              />
            );
          })
        }
      </div>
    );
  }
}

ShufflingCardGrid.defaultProps = {
  cards: [],
  padding: 10,
  itemWidth: 200,
  itemHeight: 240
};
<commit_msg>Add key to ShuffingCardGrid fakes<commit_after>import React, { Component } from 'react';
import _ from 'lodash';

import Card from './Card';

import './ShufflingCardGrid.css';

/*
  Card objects should be:
  {
    key: UUID,
    text: string
    accepted: boolean
    rejected: boolean -- should have?
  }
*/

export default class ShufflingCardGrid extends Component {

  constructor(props, context) {
    super(props, context);
    this.state = {
      centered: false,
      cardIndex: _.fromPairs(
        this.props.cards.map((c, index) => [c.key, index])
      )
    };
    // Just for demo purposes
    window.addEventListener('click', this.shuffle);
  }

  shuffle = () => {
    let n = this.props.cards.length;
    let newCards = this.props.cards.slice();
    for (let idx of _.range(n)) {
      let swapIdx = _.random(idx, n - 1);
      let [a, b] = [newCards[idx], newCards[swapIdx]];
      newCards[idx] = b;
      newCards[swapIdx] = a;
    }
    this.setState({
      // centered: true,
      cardIndex: _.fromPairs(
        newCards.map((c, index) => [c.key, index])
      )
    });
    setTimeout(() => this.setState({ centered: false }), 400);
  }

  render() {
    const rowSize = Math.floor(this.props.width / this.props.itemWidth);

    const indexToXPos = index => {
      const col = index % rowSize;
      const padding = (col + 1) * this.props.padding;
      return (col * this.props.itemWidth) + padding;
    }
    const indexToYPos = index => {
      const row = Math.floor(index / rowSize);
      const padding = (row + 1) * this.props.padding;
      return (row * this.props.itemHeight) + padding;
    }
    const transitionTime = index => {
      return 400 + (index % 6 * 50);
    }

    const actualWidth = indexToXPos(rowSize - 1) + this.props.padding + this.props.itemWidth;

    const centeredX = (this.props.width - this.props.itemWidth) / 2;
    const centeredY = (this.props.height - this.props.itemHeight) / 2;

    return (
      <div
        style={{ width: actualWidth }}
        className="ShufflingCardGrid"
        >
        {
          this.props.cards.map((_, index) => (
            <div
              key={index}
              className="Card fake"
              style={{
                top: indexToYPos(index),
                left: indexToXPos(index),
                width: this.props.itemWidth,
                height: this.props.itemHeight
              }}
            />
          ))
        }
        {
          this.props.cards.map((card, cardIndex) => {
            let index = this.state.cardIndex[card.key];
            return (
              <Card
                {...card}
                style={{
                  top: this.state.centered ? centeredY : indexToYPos(index),
                  left: this.state.centered ? centeredX : indexToXPos(index),
                  width: this.props.itemWidth,
                  height: this.props.itemHeight,
                  transition: `all ${transitionTime(cardIndex)}ms ease-in-out, box-shadow 80ms linear, background-color 80ms linear`
                }}
                onClick={(e) => {
                  e.stopPropagation();
                  this.props.onCardMark(card.key, 'selected');
                }}
              />
            );
          })
        }
      </div>
    );
  }
}

ShufflingCardGrid.defaultProps = {
  cards: [],
  padding: 10,
  itemWidth: 200,
  itemHeight: 240
};
<|endoftext|>


<commit_before>import React from 'react';
import {shallow} from 'enzyme';

import TreeNode from '../src/components/TreeNode';
import defaultTheme from '../src/themes/default';
import defaultAnimations from '../src/themes/animations';
import defaultDecorators from '../src/components/Decorators';
import data from '../example/data';

const onToggle = jest.fn();

const renderComponent = (props = {}) => {
    const wrapper = shallow(
        <TreeNode
            node={data}
            decorators={defaultDecorators}
            animations={defaultAnimations}
            style={defaultTheme}
            onToggle={onToggle}
            {...props}
        />
    );
    wrapper.nodeHeader = () => wrapper.find('NodeHeader');
    wrapper.drawer = () => wrapper.find('Drawer');
    wrapper.loading = () => wrapper.drawer().find('Loading');
    wrapper.simulateClickOnHeader = () => wrapper.nodeHeader().simulate('click');
    return wrapper;
};

describe('<TreeNode/>', () => {
    describe('when NodeHeader is clicked', () => {
        it('should call onToggle with the selected node and toggled in false', () => {
            const wrapper = renderComponent();
            wrapper.simulateClickOnHeader();
            expect(onToggle).toHaveBeenCalled();
            expect(onToggle).toBeCalledWith(data, false);
        });
    });

    describe('> <Drawer/>', () => {
        describe('when toggle is false', () => {
            it('should have children.size to be 0', () => {
                const wrapper = renderComponent({
                    node: {...data, toggled: false}
                });
                expect(wrapper.drawer().children().length).toBe(0);
            });
        });

        describe('when node has property loading in true', () => {
            it('should render a Loading component', () => {
                const wrapper = renderComponent({
                    node: {id: 1, name: 'test', toggled: true, loading: true}
                });
                expect(wrapper.loading().exists()).toBe(true);
            });
        });

        it('should return seven TreeNode children', () => {
            const wrapper = renderComponent();
            const ul = wrapper.drawer().children();
            expect(ul.children()).toHaveLength(7);
        });
    });

    describe('animations', () => {
        describe('when animations flag is false', () => {
            describe('and toggled is false', () => {
                describe('and animations is called', () => {
                    it('should return an animation object with rotateZ=0 and duration=0', () => {
                        const wrapper = renderComponent({
                            animations: false,
                            node: {...data, toggled: false}
                        });
                        const animations = wrapper.instance().animations();
                        expect(animations).toEqual({
                            toggle: {
                                animation: {rotateZ: 0},
                                duration: 0
                            }
                        });
                    });
                });
            });

            describe('and toggled is true', () => {
                describe('and animations is called', () => {
                    it('should return an animation object with rotateZ=90 and duration=0', () => {
                        const wrapper = renderComponent({animations: false});
                        const animations = wrapper.instance().animations();
                        expect(animations).toEqual({
                            toggle: {
                                animation: {rotateZ: 90},
                                duration: 0
                            }
                        });
                    });
                });
            });
        });
    });

    describe('> decorators', () => {
        describe('when node decorators not exists', () => {
            describe('and decorators is called', () => {
                it('should return defaultDecorators', () => {
                    const wrapper = renderComponent({animations: false});
                    const decorators = wrapper.instance().decorators();
                    expect(decorators).toEqual(defaultDecorators);
                });
            });
        });
    });
});
<commit_msg>test(treeNode): test if node.toggle is false<commit_after>import React from 'react';
import {shallow} from 'enzyme';

import TreeNode from '../src/components/TreeNode';
import defaultTheme from '../src/themes/default';
import defaultAnimations from '../src/themes/animations';
import defaultDecorators from '../src/components/Decorators';
import data from './mocks/data';

const onToggle = jest.fn();

const renderComponent = (props = {}) => {
    const wrapper = shallow(
        <TreeNode
            node={data}
            decorators={defaultDecorators}
            animations={defaultAnimations}
            style={defaultTheme}
            onToggle={onToggle}
            {...props}
        />
    );
    wrapper.nodeHeader = () => wrapper.find('NodeHeader');
    wrapper.drawer = () => wrapper.find('Drawer');
    wrapper.loading = () => wrapper.drawer().find('Loading');
    wrapper.simulateClickOnHeader = () => wrapper.nodeHeader().simulate('click');
    return wrapper;
};

describe('<TreeNode/>', () => {
    describe('when NodeHeader is clicked', () => {
        it('should call onToggle', () => {
            const wrapper = renderComponent();
            wrapper.simulateClickOnHeader();
            expect(onToggle).toHaveBeenCalled();
        });
        describe('and node.toggle is true', () => {
            it('should return the selected node and toggled in false', () => {
                const wrapper = renderComponent();
                wrapper.simulateClickOnHeader();
                expect(onToggle).toBeCalledWith(data, false);
            });
        });
        describe('and node.toggle is false', () => {
            it('should return the selected node and toggled in true', () => {
                const node = {...data, toggled: false};
                const wrapper = renderComponent({node});
                wrapper.simulateClickOnHeader();
                expect(onToggle).toBeCalledWith(node, true);
            });
        });
    });

    describe('<Drawer/>', () => {
        describe('when toggle is false', () => {
            it('should have children.size to be 0', () => {
                const wrapper = renderComponent({
                    node: {...data, toggled: false}
                });
                expect(wrapper.drawer().children().length).toBe(0);
            });
        });

        describe('when node has property loading in true', () => {
            it('should render a Loading component', () => {
                const wrapper = renderComponent({
                    node: {id: 1, name: 'test', toggled: true, loading: true}
                });
                expect(wrapper.loading().exists()).toBe(true);
            });
        });

        it('should return seven TreeNode children', () => {
            const wrapper = renderComponent();
            const ul = wrapper.drawer().children();
            expect(ul.children()).toHaveLength(7);
        });
    });

    describe('animations', () => {
        describe('when animations flag is false', () => {
            describe('and toggled is false', () => {
                describe('and animations is called', () => {
                    it('should return an animation object with rotateZ=0 and duration=0', () => {
                        const wrapper = renderComponent({
                            animations: false,
                            node: {...data, toggled: false}
                        });
                        const animations = wrapper.instance().animations();
                        expect(animations).toEqual({
                            toggle: {
                                animation: {rotateZ: 0},
                                duration: 0
                            }
                        });
                    });
                });
            });

            describe('and toggled is true', () => {
                describe('and animations is called', () => {
                    it('should return an animation object with rotateZ=90 and duration=0', () => {
                        const wrapper = renderComponent({animations: false});
                        const animations = wrapper.instance().animations();
                        expect(animations).toEqual({
                            toggle: {
                                animation: {rotateZ: 90},
                                duration: 0
                            }
                        });
                    });
                });
            });
        });
    });

    describe('decorators', () => {
        describe('when node decorators not exists', () => {
            describe('and decorators is called', () => {
                it('should return defaultDecorators', () => {
                    const wrapper = renderComponent({animations: false});
                    const decorators = wrapper.instance().decorators();
                    expect(decorators).toEqual(defaultDecorators);
                });
            });
        });
    });
});
<|endoftext|>