<commit_before>frappe.socketio = {
	open_tasks: {},
	open_docs: [],
	emit_queue: [],
	init: function(port = 3000) {
		if (frappe.boot.disable_async) {
			return;
		}

		if (frappe.socketio.socket) {
			return;
		}

		//Enable secure option when using HTTPS
		if (window.location.protocol == "https:") {
			frappe.socketio.socket = io.connect(frappe.socketio.get_host(port), {secure: true});
		}
		else if (window.location.protocol == "http:") {
			frappe.socketio.socket = io.connect(frappe.socketio.get_host(port));
		}
		else if (window.location.protocol == "file:") {
			frappe.socketio.socket = io.connect(window.localStorage.server);
		}

		if (!frappe.socketio.socket) {
			console.log("Unable to connect to " + frappe.socketio.get_host(port));
			return;
		}

		frappe.socketio.socket.on('msgprint', function(message) {
			frappe.msgprint(message);
		});

		frappe.socketio.socket.on('eval_js', function(message) {
			eval(message);
		});

		frappe.socketio.socket.on('progress', function(data) {
			if(data.progress) {
				data.percent = flt(data.progress[0]) / data.progress[1] * 100;
			}
			if(data.percent) {
				if(data.percent==100) {
					frappe.hide_progress();
				} else {
					frappe.show_progress(data.title || __("Progress"), data.percent, 100, data.description);
				}
			}
		});

		frappe.socketio.setup_listeners();
		frappe.socketio.setup_reconnect();
		frappe.socketio.uploader = new frappe.socketio.SocketIOUploader();

		$(document).on('form-load form-rename', function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			for (var i=0, l=frappe.socketio.open_docs.length; i<l; i++) {
				var d = frappe.socketio.open_docs[i];
				if (frm.doctype==d.doctype && frm.docname==d.name) {
					// already subscribed
					return false;
				}
			}

			frappe.socketio.doc_subscribe(frm.doctype, frm.docname);
		});

		$(document).on("form-refresh", function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			frappe.socketio.doc_open(frm.doctype, frm.docname);
		});

		$(document).on('form-unload', function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			// frappe.socketio.doc_unsubscribe(frm.doctype, frm.docname);
			frappe.socketio.doc_close(frm.doctype, frm.docname);
		});

		window.onbeforeunload = function() {
			if (!cur_frm || cur_frm.is_new()) {
				return;
			}

			// if tab/window is closed, notify other users
			if (cur_frm.doc) {
				frappe.socketio.doc_close(cur_frm.doctype, cur_frm.docname);
			}
		}
	},
	get_host: function(port = 3000) {
		var host = window.location.origin;
		if(window.dev_server) {
			var parts = host.split(":");
			port = frappe.boot.socketio_port || port.toString() || '3000';
			if(parts.length > 2) {
				host = parts[0] + ":" + parts[1];
			}
			host = host + ":" + port;
		}
		return host;
	},
	subscribe: function(task_id, opts) {
		// TODO DEPRECATE

		frappe.socketio.socket.emit('task_subscribe', task_id);
		frappe.socketio.socket.emit('progress_subscribe', task_id);

		frappe.socketio.open_tasks[task_id] = opts;
	},
	task_subscribe: function(task_id) {
		frappe.socketio.socket.emit('task_subscribe', task_id);
	},
	task_unsubscribe: function(task_id) {
		frappe.socketio.socket.emit('task_unsubscribe', task_id);
	},
	doc_subscribe: function(doctype, docname) {
		if (frappe.flags.doc_subscribe) {
			console.log('throttled');
			return;
		}

		frappe.flags.doc_subscribe = true;

		// throttle to 1 per sec
		setTimeout(function() { frappe.flags.doc_subscribe = false }, 1000);

		frappe.socketio.socket.emit('doc_subscribe', doctype, docname);
		frappe.socketio.open_docs.push({doctype: doctype, docname: docname});
	},
	doc_unsubscribe: function(doctype, docname) {
		frappe.socketio.socket.emit('doc_unsubscribe', doctype, docname);
		frappe.socketio.open_docs = $.filter(frappe.socketio.open_docs, function(d) {
			if(d.doctype===doctype && d.name===docname) {
				return null;
			} else {
				return d;
			}
		})
	},
	doc_open: function(doctype, docname) {
		// notify that the user has opened this doc, if not already notified
		if(!frappe.socketio.last_doc
			|| (frappe.socketio.last_doc[0]!=doctype && frappe.socketio.last_doc[0]!=docname)) {
			frappe.socketio.socket.emit('doc_open', doctype, docname);
		}
		frappe.socketio.last_doc = [doctype, docname];
	},
	doc_close: function(doctype, docname) {
		// notify that the user has closed this doc
		frappe.socketio.socket.emit('doc_close', doctype, docname);
	},

	setup_listeners: function() {
		frappe.socketio.socket.on('task_status_change', function(data) {
			frappe.socketio.process_response(data, data.status.toLowerCase());
		});
		frappe.socketio.socket.on('task_progress', function(data) {
			frappe.socketio.process_response(data, "progress");
		});
	},
	setup_reconnect: function() {
		// subscribe again to open_tasks
		frappe.socketio.socket.on("connect", function() {
			// wait for 5 seconds before subscribing again
			// because it takes more time to start python server than nodejs server
			// and we use validation requests to python server for subscribing
			setTimeout(function() {
				$.each(frappe.socketio.open_tasks, function(task_id, opts) {
					frappe.socketio.subscribe(task_id, opts);
				});

				// re-connect open docs
				$.each(frappe.socketio.open_docs, function(d) {
					if(locals[d.doctype] && locals[d.doctype][d.name]) {
						frappe.socketio.doc_subscribe(d.doctype, d.name);
					}
				});

				if (cur_frm && cur_frm.doc) {
					frappe.socketio.doc_open(cur_frm.doc.doctype, cur_frm.doc.name);
				}
			}, 5000);
		});
	},
	process_response: function(data, method) {
		if(!data) {
			return;
		}

		// success
		var opts = frappe.socketio.open_tasks[data.task_id];
		if(opts[method]) {
			opts[method](data);
		}

		// "callback" is std frappe term
		if(method==="success") {
			if(opts.callback) opts.callback(data);
		}

		// always
		frappe.request.cleanup(opts, data);
		if(opts.always) {
			opts.always(data);
		}

		// error
		if(data.status_code && data.status_code > 400 && opts.error) {
			opts.error(data);
		}
	}
}

frappe.provide("frappe.realtime");
frappe.realtime.on = function(event, callback) {
	frappe.socketio.socket && frappe.socketio.socket.on(event, callback);
};

frappe.realtime.off = function(event, callback) {
	frappe.socketio.socket && frappe.socketio.socket.off(event, callback);
}

frappe.realtime.publish = function(event, message) {
	if(frappe.socketio.socket) {
		frappe.socketio.socket.emit(event, message);
	}
}

frappe.socketio.SocketIOUploader = class SocketIOUploader {
	constructor() {
		frappe.socketio.socket.on('upload-request-slice', (data) => {
			var place = data.currentSlice * this.chunk_size,
				slice = this.file.slice(place,
					place + Math.min(this.chunk_size, this.file.size - place));

			if (this.on_progress) {
				// update progress
				this.on_progress(place / this.file.size * 100);
			}

			this.reader.readAsArrayBuffer(slice);
			this.started = true;
			this.keep_alive();
		});

		frappe.socketio.socket.on('upload-end', (data) => {
			this.reader = null;
			this.file = null;
			if (data.file_url.substr(0, 7)==='/public') {
				data.file_url = data.file_url.substr(7);
			}
			this.callback(data);
		});

		frappe.socketio.socket.on('upload-error', (data) => {
			this.disconnect(false);
			frappe.msgprint({
				title: __('Upload Failed'),
				message: data.error,
				indicator: 'red'
			});
		});

		frappe.socketio.socket.on('disconnect', () => {
			this.disconnect();
		});
	}

	start({file=null, is_private=0, filename='', callback=null, on_progress=null,
		chunk_size=24576, fallback=null} = {}) {

		if (this.reader) {
			frappe.throw(__('File Upload in Progress. Please try again in a few moments.'));
		}

		function fallback_required() {
			return !frappe.boot.sysdefaults.use_socketio_to_upload_file || !frappe.socketio.socket.connected;
		}

		if (fallback_required()) {
			return fallback ? fallback() : frappe.throw(__('Socketio is not connected. Cannot upload'));
		}

		this.reader = new FileReader();
		this.file = file;
		this.chunk_size = chunk_size;
		this.callback = callback;
		this.on_progress = on_progress;
		this.fallback = fallback;
		this.started = false;

		this.reader.onload = () => {
			frappe.socketio.socket.emit('upload-accept-slice', {
				is_private: is_private,
				name: filename,
				type: this.file.type,
				size: this.file.size,
				data: this.reader.result
			});
			this.keep_alive();
		};

		var slice = file.slice(0, this.chunk_size);
		this.reader.readAsArrayBuffer(slice);
	}

	keep_alive() {
		if (this.next_check) {
			clearTimeout (this.next_check);
		}
		this.next_check = setTimeout (() => {
			if (!this.started) {
				// upload never started, so try fallback
				if (this.fallback) {
					this.fallback();
				} else {
					this.disconnect();
				}
			}
			this.disconnect();
		}, 3000);
	}

	disconnect(with_message = true) {
		if (this.reader) {
			this.reader = null;
			this.file = null;
			frappe.hide_progress();
			if (with_message) {
				frappe.msgprint({
					title: __('File Upload'),
					message: __('File Upload Disconnected. Please try again.'),
					indicator: 'red'
				});
			}
		}
	}
}
<commit_msg>fix(socketio): Return early if socketio not found<commit_after>frappe.socketio = {
	open_tasks: {},
	open_docs: [],
	emit_queue: [],
	init: function(port = 3000) {
		if (!window.io) {
			return;
		}

		if (frappe.boot.disable_async) {
			return;
		}

		if (frappe.socketio.socket) {
			return;
		}

		//Enable secure option when using HTTPS
		if (window.location.protocol == "https:") {
			frappe.socketio.socket = io.connect(frappe.socketio.get_host(port), {secure: true});
		}
		else if (window.location.protocol == "http:") {
			frappe.socketio.socket = io.connect(frappe.socketio.get_host(port));
		}
		else if (window.location.protocol == "file:") {
			frappe.socketio.socket = io.connect(window.localStorage.server);
		}

		if (!frappe.socketio.socket) {
			console.log("Unable to connect to " + frappe.socketio.get_host(port));
			return;
		}

		frappe.socketio.socket.on('msgprint', function(message) {
			frappe.msgprint(message);
		});

		frappe.socketio.socket.on('eval_js', function(message) {
			eval(message);
		});

		frappe.socketio.socket.on('progress', function(data) {
			if(data.progress) {
				data.percent = flt(data.progress[0]) / data.progress[1] * 100;
			}
			if(data.percent) {
				if(data.percent==100) {
					frappe.hide_progress();
				} else {
					frappe.show_progress(data.title || __("Progress"), data.percent, 100, data.description);
				}
			}
		});

		frappe.socketio.setup_listeners();
		frappe.socketio.setup_reconnect();
		frappe.socketio.uploader = new frappe.socketio.SocketIOUploader();

		$(document).on('form-load form-rename', function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			for (var i=0, l=frappe.socketio.open_docs.length; i<l; i++) {
				var d = frappe.socketio.open_docs[i];
				if (frm.doctype==d.doctype && frm.docname==d.name) {
					// already subscribed
					return false;
				}
			}

			frappe.socketio.doc_subscribe(frm.doctype, frm.docname);
		});

		$(document).on("form-refresh", function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			frappe.socketio.doc_open(frm.doctype, frm.docname);
		});

		$(document).on('form-unload', function(e, frm) {
			if (frm.is_new()) {
				return;
			}

			// frappe.socketio.doc_unsubscribe(frm.doctype, frm.docname);
			frappe.socketio.doc_close(frm.doctype, frm.docname);
		});

		window.onbeforeunload = function() {
			if (!cur_frm || cur_frm.is_new()) {
				return;
			}

			// if tab/window is closed, notify other users
			if (cur_frm.doc) {
				frappe.socketio.doc_close(cur_frm.doctype, cur_frm.docname);
			}
		}
	},
	get_host: function(port = 3000) {
		var host = window.location.origin;
		if(window.dev_server) {
			var parts = host.split(":");
			port = frappe.boot.socketio_port || port.toString() || '3000';
			if(parts.length > 2) {
				host = parts[0] + ":" + parts[1];
			}
			host = host + ":" + port;
		}
		return host;
	},
	subscribe: function(task_id, opts) {
		// TODO DEPRECATE

		frappe.socketio.socket.emit('task_subscribe', task_id);
		frappe.socketio.socket.emit('progress_subscribe', task_id);

		frappe.socketio.open_tasks[task_id] = opts;
	},
	task_subscribe: function(task_id) {
		frappe.socketio.socket.emit('task_subscribe', task_id);
	},
	task_unsubscribe: function(task_id) {
		frappe.socketio.socket.emit('task_unsubscribe', task_id);
	},
	doc_subscribe: function(doctype, docname) {
		if (frappe.flags.doc_subscribe) {
			console.log('throttled');
			return;
		}

		frappe.flags.doc_subscribe = true;

		// throttle to 1 per sec
		setTimeout(function() { frappe.flags.doc_subscribe = false }, 1000);

		frappe.socketio.socket.emit('doc_subscribe', doctype, docname);
		frappe.socketio.open_docs.push({doctype: doctype, docname: docname});
	},
	doc_unsubscribe: function(doctype, docname) {
		frappe.socketio.socket.emit('doc_unsubscribe', doctype, docname);
		frappe.socketio.open_docs = $.filter(frappe.socketio.open_docs, function(d) {
			if(d.doctype===doctype && d.name===docname) {
				return null;
			} else {
				return d;
			}
		})
	},
	doc_open: function(doctype, docname) {
		// notify that the user has opened this doc, if not already notified
		if(!frappe.socketio.last_doc
			|| (frappe.socketio.last_doc[0]!=doctype && frappe.socketio.last_doc[0]!=docname)) {
			frappe.socketio.socket.emit('doc_open', doctype, docname);
		}
		frappe.socketio.last_doc = [doctype, docname];
	},
	doc_close: function(doctype, docname) {
		// notify that the user has closed this doc
		frappe.socketio.socket.emit('doc_close', doctype, docname);
	},

	setup_listeners: function() {
		frappe.socketio.socket.on('task_status_change', function(data) {
			frappe.socketio.process_response(data, data.status.toLowerCase());
		});
		frappe.socketio.socket.on('task_progress', function(data) {
			frappe.socketio.process_response(data, "progress");
		});
	},
	setup_reconnect: function() {
		// subscribe again to open_tasks
		frappe.socketio.socket.on("connect", function() {
			// wait for 5 seconds before subscribing again
			// because it takes more time to start python server than nodejs server
			// and we use validation requests to python server for subscribing
			setTimeout(function() {
				$.each(frappe.socketio.open_tasks, function(task_id, opts) {
					frappe.socketio.subscribe(task_id, opts);
				});

				// re-connect open docs
				$.each(frappe.socketio.open_docs, function(d) {
					if(locals[d.doctype] && locals[d.doctype][d.name]) {
						frappe.socketio.doc_subscribe(d.doctype, d.name);
					}
				});

				if (cur_frm && cur_frm.doc) {
					frappe.socketio.doc_open(cur_frm.doc.doctype, cur_frm.doc.name);
				}
			}, 5000);
		});
	},
	process_response: function(data, method) {
		if(!data) {
			return;
		}

		// success
		var opts = frappe.socketio.open_tasks[data.task_id];
		if(opts[method]) {
			opts[method](data);
		}

		// "callback" is std frappe term
		if(method==="success") {
			if(opts.callback) opts.callback(data);
		}

		// always
		frappe.request.cleanup(opts, data);
		if(opts.always) {
			opts.always(data);
		}

		// error
		if(data.status_code && data.status_code > 400 && opts.error) {
			opts.error(data);
		}
	}
}

frappe.provide("frappe.realtime");
frappe.realtime.on = function(event, callback) {
	frappe.socketio.socket && frappe.socketio.socket.on(event, callback);
};

frappe.realtime.off = function(event, callback) {
	frappe.socketio.socket && frappe.socketio.socket.off(event, callback);
}

frappe.realtime.publish = function(event, message) {
	if(frappe.socketio.socket) {
		frappe.socketio.socket.emit(event, message);
	}
}

frappe.socketio.SocketIOUploader = class SocketIOUploader {
	constructor() {
		frappe.socketio.socket.on('upload-request-slice', (data) => {
			var place = data.currentSlice * this.chunk_size,
				slice = this.file.slice(place,
					place + Math.min(this.chunk_size, this.file.size - place));

			if (this.on_progress) {
				// update progress
				this.on_progress(place / this.file.size * 100);
			}

			this.reader.readAsArrayBuffer(slice);
			this.started = true;
			this.keep_alive();
		});

		frappe.socketio.socket.on('upload-end', (data) => {
			this.reader = null;
			this.file = null;
			if (data.file_url.substr(0, 7)==='/public') {
				data.file_url = data.file_url.substr(7);
			}
			this.callback(data);
		});

		frappe.socketio.socket.on('upload-error', (data) => {
			this.disconnect(false);
			frappe.msgprint({
				title: __('Upload Failed'),
				message: data.error,
				indicator: 'red'
			});
		});

		frappe.socketio.socket.on('disconnect', () => {
			this.disconnect();
		});
	}

	start({file=null, is_private=0, filename='', callback=null, on_progress=null,
		chunk_size=24576, fallback=null} = {}) {

		if (this.reader) {
			frappe.throw(__('File Upload in Progress. Please try again in a few moments.'));
		}

		function fallback_required() {
			return !frappe.boot.sysdefaults.use_socketio_to_upload_file || !frappe.socketio.socket.connected;
		}

		if (fallback_required()) {
			return fallback ? fallback() : frappe.throw(__('Socketio is not connected. Cannot upload'));
		}

		this.reader = new FileReader();
		this.file = file;
		this.chunk_size = chunk_size;
		this.callback = callback;
		this.on_progress = on_progress;
		this.fallback = fallback;
		this.started = false;

		this.reader.onload = () => {
			frappe.socketio.socket.emit('upload-accept-slice', {
				is_private: is_private,
				name: filename,
				type: this.file.type,
				size: this.file.size,
				data: this.reader.result
			});
			this.keep_alive();
		};

		var slice = file.slice(0, this.chunk_size);
		this.reader.readAsArrayBuffer(slice);
	}

	keep_alive() {
		if (this.next_check) {
			clearTimeout (this.next_check);
		}
		this.next_check = setTimeout (() => {
			if (!this.started) {
				// upload never started, so try fallback
				if (this.fallback) {
					this.fallback();
				} else {
					this.disconnect();
				}
			}
			this.disconnect();
		}, 3000);
	}

	disconnect(with_message = true) {
		if (this.reader) {
			this.reader = null;
			this.file = null;
			frappe.hide_progress();
			if (with_message) {
				frappe.msgprint({
					title: __('File Upload'),
					message: __('File Upload Disconnected. Please try again.'),
					indicator: 'red'
				});
			}
		}
	}
}
<|endoftext|>


<commit_before>
import * as THREE from 'three';
import { intersectTris, intersectClosestTri } from './GeometryUtilities.js';
import { arrayToBox } from './BoundsUtilities.js';

const boundingBox = new THREE.Box3();
const boxIntersection = new THREE.Vector3();
const xyzFields = [ 'x', 'y', 'z' ];

export default
class MeshBVHNode {

	constructor() {

		// internal nodes have boundingData, children, and splitAxis
		// leaf nodes have offset and count (referring to primitives in the mesh geometry)

	}

	intersectRay( ray, target ) {

		arrayToBox( this.boundingData, boundingBox );

		return ray.intersectBox( boundingBox, target );

	}

	raycast( mesh, raycaster, ray, intersects ) {

		if ( this.count ) intersectTris( mesh, mesh.geometry, raycaster, ray, this.offset, this.count, intersects );
		else this.children.forEach( c => {

			if ( c.intersectRay( ray, boxIntersection ) )
				c.raycast( mesh, raycaster, ray, intersects );

		} );

	}

	raycastFirst( mesh, raycaster, ray ) {

		if ( this.count ) {

			return intersectClosestTri( mesh, mesh.geometry, raycaster, ray, this.offset, this.count );

		} else {

			// consider the position of the split plane with respect to the oncoming ray; whichever direction
			// the ray is coming from, look for an intersection among that side of the tree first
			const children = this.children;
			const splitAxis = this.splitAxis;
			const xyzAxis = xyzFields[ splitAxis ];
			const rayDir = ray.direction[ xyzAxis ];
			const leftToRight = rayDir >= 0;

			let c1, c2;
			if ( leftToRight ) {

				c1 = children[ 0 ];
				c2 = children[ 1 ];

			} else {

				c1 = children[ 1 ];
				c2 = children[ 0 ];

			}

			const c1Intersection = c1.intersectRay( ray, boxIntersection );
			const c1Result = c1Intersection ? c1.raycastFirst( mesh, raycaster, ray ) : null;

			// if we got an intersection in the first node and it's closer than the second node's bounding
			// box, we don't need to consider the second node because it couldn't possibly be a better result

			if ( c1Result ) {

				const rayOrig = ray.origin[ xyzAxis ];
				const toPoint = rayOrig - c1Result.point[ xyzAxis ];
				const toChild1 = rayOrig - c2.boundingData[ splitAxis ];
				const toChild2 = rayOrig - c2.boundingData[ splitAxis + 3 ];

				const toPoint2 = toPoint * toPoint;
				if ( toPoint2 <= toChild1 * toChild1 && toPoint2 <= toChild2 * toChild2 ) {

					return c1Result;

				}

			}

			// either there was no intersection in the first node, or there could still be a closer
			// intersection in the second, so check the second node and then take the better of the two
			const c2Intersection = c2.intersectRay( ray, boxIntersection );
			const c2Result = c2Intersection ? c2.raycastFirst( mesh, raycaster, ray ) : null;

			if ( c1Result && c2Result ) {

				return c1Result.distance <= c2Result.distance ? c1Result : c2Result;

			} else {

				return c1Result || c2Result || null;

			}

		}

	}

}
<commit_msg>comments<commit_after>
import * as THREE from 'three';
import { intersectTris, intersectClosestTri } from './GeometryUtilities.js';
import { arrayToBox } from './BoundsUtilities.js';

const boundingBox = new THREE.Box3();
const boxIntersection = new THREE.Vector3();
const xyzFields = [ 'x', 'y', 'z' ];

export default
class MeshBVHNode {

	constructor() {

		// internal nodes have boundingData, children, and splitAxis
		// leaf nodes have offset and count (referring to primitives in the mesh geometry)

	}

	intersectRay( ray, target ) {

		arrayToBox( this.boundingData, boundingBox );

		return ray.intersectBox( boundingBox, target );

	}

	raycast( mesh, raycaster, ray, intersects ) {

		if ( this.count ) intersectTris( mesh, mesh.geometry, raycaster, ray, this.offset, this.count, intersects );
		else this.children.forEach( c => {

			if ( c.intersectRay( ray, boxIntersection ) )
				c.raycast( mesh, raycaster, ray, intersects );

		} );

	}

	raycastFirst( mesh, raycaster, ray ) {

		if ( this.count ) {

			return intersectClosestTri( mesh, mesh.geometry, raycaster, ray, this.offset, this.count );

		} else {

			// consider the position of the split plane with respect to the oncoming ray; whichever direction
			// the ray is coming from, look for an intersection among that side of the tree first
			const children = this.children;
			const splitAxis = this.splitAxis;
			const xyzAxis = xyzFields[ splitAxis ];
			const rayDir = ray.direction[ xyzAxis ];
			const leftToRight = rayDir >= 0;

			// c1 is the child to check first
			let c1, c2;
			if ( leftToRight ) {

				c1 = children[ 0 ];
				c2 = children[ 1 ];

			} else {

				c1 = children[ 1 ];
				c2 = children[ 0 ];

			}

			const c1Intersection = c1.intersectRay( ray, boxIntersection );
			const c1Result = c1Intersection ? c1.raycastFirst( mesh, raycaster, ray ) : null;

			// if we got an intersection in the first node and it's closer than the second node's bounding
			// box, we don't need to consider the second node because it couldn't possibly be a better result
			if ( c1Result ) {

				// check only along the split axis
				const rayOrig = ray.origin[ xyzAxis ];
				const toPoint = rayOrig - c1Result.point[ xyzAxis ];
				const toChild1 = rayOrig - c2.boundingData[ splitAxis ];
				const toChild2 = rayOrig - c2.boundingData[ splitAxis + 3 ];

				const toPointSq = toPoint * toPoint;
				if ( toPointSq <= toChild1 * toChild1 && toPointSq <= toChild2 * toChild2 ) {

					return c1Result;

				}

			}

			// either there was no intersection in the first node, or there could still be a closer
			// intersection in the second, so check the second node and then take the better of the two
			const c2Intersection = c2.intersectRay( ray, boxIntersection );
			const c2Result = c2Intersection ? c2.raycastFirst( mesh, raycaster, ray ) : null;

			if ( c1Result && c2Result ) {

				return c1Result.distance <= c2Result.distance ? c1Result : c2Result;

			} else {

				return c1Result || c2Result || null;

			}

		}

	}

}
<|endoftext|>


<commit_before>import React from 'react';

import Button from 'react-bootstrap/Button';

import Header from '../Header/';
import SaveTripModal from './save-trip-modal.js'
import TripsContainer from './trips-container.js';

/**
 * A trip object containing the data stored in a trip document in Firestore.
 * @typedef {Object} TripData
 * @property {string} title The trips's title.
 * @property {string} description A description of the trip.
 * @property {string} destination The general destination of the trip.
 * @property {firebase.firestore.Timestamp} start_date Start date Firestore
 *     timestamp object.
 * @property {firebase.firestore.Timestamp} end_date End date Firestore
 *     timestamp object
 * @property {!string[]} collaborators An array of collaborator uids.
 */

/**
 * ViewTrips component that defines the page where a user can view and manage
 * their current trips.
 */
class ViewTrips extends React.Component {
  /** @override */
  constructor() {
    super();
    this.state = { showModal: false,
                   refreshSaveTripModal: false,
                   tripId: null,
                   defaultFormObj: null,
                 };
  }

  /**
   * Handler that flips `refreshSaveTripModal` property which causes that
   * save/edit trip component to be reloaded.
   *
   * This was done to prevent bugs where multiple component input fields would
   * persist from one instance of the modal to the next when view trips page
   * was not refreshed in between.
   */
  refreshSaveTripModal = () => {
    this.setState({ refreshSaveTripModal: !this.state.refreshSaveTripModal });
  }

  /** Handler that hides the add/edit trip page. */
  hideSaveTripModal = () => { this.setState({ showModal: false }); }

  /** Handler that refreshes and displays the add/edit trip page. */
  showSaveTripModal = () => {
    this.refreshSaveTripModal();
    this.setState({ showModal: true });
  }

  /**
   * Handler that displays the add trip page.
   *
   * Sets state for the states `tripId` and `placeholderObj` in order
   * to ensure the modal has the visual characteristics of an "add trip" modal
   * and creates a new Trip document in the database.
   */
  showAddTripModal = () => {
    this.setState({
      tripId: null,
      defaultFormObj: null,
    });
    this.showSaveTripModal();
  }

  /**
   * Handler that displays the edit trip page.
   *
   * Sets state for the states `tripId` and `placeholderObj` in order
   * to ensure the modal has the visual characteristics of an "edit trip" modal
   * and overwrites and existing Trip document in the database.
   *
  * @param {string} tripId Document ID for the current Trip document.
  * @param {!TripData} tripData Contains the data for the Trip document that
  *     that will be editted.
   */
  showEditTripModal = (tripId, tripData) => {
    this.setState({
      tripId: tripId,
      defaultFormObj: tripData
    });
    this.showSaveTripModal();
  }

  /** @override */
  render() {
    return (

        <SaveTripModal
          show={this.state.showModal}
          handleClose={this.hideSaveTripModal}
          tripId={this.state.tripId}
          defaultFormObj={this.state.defaultFormObj}
          key={this.state.refreshSaveTripModal}
        />

          <Button type='button' onClick={this.showAddTripModal}>
            + New Trip
          </Button>

        <TripsContainer
          handleEditTrip={this.showEditTripModal}
        />

    );
  }
}

export default ViewTrips;
<commit_msg>Fix naming defaultformData naming in index.js.<commit_after>import React from 'react';

import Button from 'react-bootstrap/Button';

import Header from '../Header/';
import SaveTripModal from './save-trip-modal.js'
import TripsContainer from './trips-container.js';

/**
 * A trip object containing the data stored in a trip document in Firestore.
 * @typedef {Object} TripData
 * @property {string} title The trips's title.
 * @property {string} description A description of the trip.
 * @property {string} destination The general destination of the trip.
 * @property {firebase.firestore.Timestamp} start_date Start date Firestore
 *     timestamp object.
 * @property {firebase.firestore.Timestamp} end_date End date Firestore
 *     timestamp object
 * @property {!string[]} collaborators An array of collaborator uids.
 */

/**
 * ViewTrips component that defines the page where a user can view and manage
 * their current trips.
 */
class ViewTrips extends React.Component {
  /** @override */
  constructor() {
    super();
    this.state = { showModal: false,
                   refreshSaveTripModal: false,
                   tripId: null,
                   defaultFormData: null,
                 };
  }

  /**
   * Handler that flips `refreshSaveTripModal` property which causes that
   * save/edit trip component to be reloaded.
   *
   * This was done to prevent bugs where multiple component input fields would
   * persist from one instance of the modal to the next when view trips page
   * was not refreshed in between.
   */
  refreshSaveTripModal = () => {
    this.setState({ refreshSaveTripModal: !this.state.refreshSaveTripModal });
  }

  /** Handler that hides the add/edit trip page. */
  hideSaveTripModal = () => { this.setState({ showModal: false }); }

  /** Handler that refreshes and displays the add/edit trip page. */
  showSaveTripModal = () => {
    this.refreshSaveTripModal();
    this.setState({ showModal: true });
  }

  /**
   * Handler that displays the add trip page.
   *
   * Sets state for the states `tripId` and `placeholderObj` in order
   * to ensure the modal has the visual characteristics of an "add trip" modal
   * and creates a new Trip document in the database.
   */
  showAddTripModal = () => {
    this.setState({
      tripId: null,
      defaultFormData: null,
    });
    this.showSaveTripModal();
  }

  /**
   * Handler that displays the edit trip page.
   *
   * Sets state for the states `tripId` and `placeholderObj` in order
   * to ensure the modal has the visual characteristics of an "edit trip" modal
   * and overwrites and existing Trip document in the database.
   *
  * @param {string} tripId Document ID for the current Trip document.
  * @param {!TripData} tripData Contains the data for the Trip document that
  *     that will be editted.
   */
  showEditTripModal = (tripId, tripData) => {
    console.log(tripData);
    this.setState({
      tripId: tripId,
      defaultFormData: tripData,
    });
    this.showSaveTripModal();
  }

  /** @override */
  render() {
    return (

        <SaveTripModal
          show={this.state.showModal}
          handleClose={this.hideSaveTripModal}
          tripId={this.state.tripId}
          defaultFormData={this.state.defaultFormData}
          key={this.state.refreshSaveTripModal}
        />

          <Button type='button' onClick={this.showAddTripModal}>
            + New Trip
          </Button>

        <TripsContainer
          handleEditTrip={this.showEditTripModal}
        />

    );
  }
}

export default ViewTrips;
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import 'typeface-roboto'
import {
  withStyles,
  Grid,
  Typography,
  Divider,
  List,
  ListItem,
  ListItemText,
  ListItemIcon,
  Avatar,
  Tabs,
  Tab,
  AppBar,
  Button
} from '@material-ui/core'

import {
  AccountBalanceWallet,
  Work,
  Apps,
  Assignment,
  GroupWork,
  Archive,
  CardMembership,
  BugReport,
  ArrowForward,
} from '@material-ui/icons'

import scrollToComponent from 'react-scroll-to-component'
import { injectIntl, FormattedMessage, FormattedHTMLMessage } from 'react-intl'

import './mailchimp.css'

import TopBarContainer from '../../containers/topbar'
import InfoContainer from '../../containers/info'
import Bottom from '../../components/bottom/bottom'
import LoginButton from '../../components/session/login-button'
import Pricing from './pricing'
import messages from './messages'
import OurStack from './components/OurStack'
import mainStyles from '../styles/style'
import SubscribeForm from '../form/subscribe-form'

const freelancerImage = require('../../images/welcome-freelancer.png')
const companiesImage = require('../../images/welcome-companies.png')
const teamImage = require('../../images/welcome-teamwork.png')
const appSnapshotImage = require('../../images/gitpay-app.png')
const citySoftware = require('../../images/city-software.png')
const deal = require('../../images/deal.png')

import {
  MainTitle,
  MainList,
  MainBanner,
  ResponsiveImage,
  ShadowImage,
  Section
} from './components/CommonStyles'

const styles = (theme) => mainStyles(theme)

class Welcome extends Component {
  constructor (props) {
    super(props)

    this.state = {
      value: 0
    }

    this.handleSectionTab = this.handleSectionTab.bind(this)
  }

  handleSectionTab = (event, value) => {
    this.setState({ value })
    let offset = -30
    if (event.currentTarget.id === 'integrations') offset = -50
    scrollToComponent(this.refs[event.currentTarget.id], {
      offset: offset,
      align: 'top',
      ease: 'inExpo'
    })
  }

  render () {
    const { classes, location } = this.props

    return (

        <TopBarContainer ref='intro' />
        <AppBar position='sticky' color='default'>
          <Tabs variant='fullWidth' value={ this.state.value } onChange={ this.handleSectionTab }>
            <Tab id='intro' value={ 0 } label={ this.props.intl.formatMessage(messages.topMenu1) } />
            <Tab id='contrib' value={ 1 } label={ this.props.intl.formatMessage(messages.topMenu2) } />
            <Tab id='companies' value={ 2 } label={ this.props.intl.formatMessage(messages.topMenu3) } />
            <Tab id='collab' value={ 3 } label={ this.props.intl.formatMessage(messages.topMenu4) } />
            <Tab id='how-it-works' value={ 4 } label={ this.props.intl.formatMessage(messages.topMenu5) } />
            <Tab id='pricing' value={ 5 } label={ this.props.intl.formatMessage(messages.topMenu6) } />
            <Tab id='integrations' value={ 6 } label={ this.props.intl.formatMessage(messages.topMenu7) } />
            <Tab id='get-started' value={ 7 } label={ this.props.intl.formatMessage(messages.topMenu8) } />
          </Tabs>
        </AppBar>
        <MainBanner>
          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } style={ { padding: 0, margin: 0 } }>

                <Typography className={ classes.tagline } gutterBottom>
                  <FormattedMessage id='welcome.tagline' defaultMessage='Welcome to Gitpay' />
                </Typography>
                <Typography variant='h6' gutterBottom>
                  <FormattedMessage id='welcome.tagline1' defaultMessage='Work in tasks on demand' />
                </Typography>
                <Typography type='subtitle1' gutterBottom noWrap>
                  <FormattedHTMLMessage
                    id='welcome.tagline2' defaultMessage='and receive bounty for your contributions' />
                </Typography>

                  <SubscribeForm type='subscribe-form-main' />

                <LoginButton referer={ location } contrast includeForm={ false } />

              <InfoContainer />
              <OurStack />
            </Grid>
          </Grid>
        </MainBanner>

          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 6 }>
              <MainTitle left>
                <Typography variant='h5' gutterBottom>
                  <FormattedMessage id='welcome.headline.forfreelancers' defaultMessage='For freelancers' />
                </Typography>
              </MainTitle>
              <MainList>
                <List>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Apps />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeFreelancersItemOnePrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeFreelancersItemOneSecondary) }
                    />
                  </ListItem>

                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Work />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeFreelancersItemTwoPrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeFreelancersItemTwoSecondary) }
                    />
                  </ListItem>

                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <AccountBalanceWallet />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeFreelancersItemThreePrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeFreelancersItemThreeSecondary) }
                    />
                  </ListItem>

                </List>
              </MainList>
            </Grid>
            <Grid item xs={ 12 } sm={ 6 }>
              <ResponsiveImage width='600' src={ freelancerImage } />
            </Grid>
          </Grid>

          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 6 }>
              <MainTitle left>
                <Typography variant='h5' gutterBottom>
                  <FormattedMessage id='welcome.tagline.companies.main.headline' defaultMessage='For companies' />
                </Typography>
              </MainTitle>
              <MainList>
                <List>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Assignment />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeCompaniesItemOnePrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeCompaniesItemOneSecondary) }
                    />
                  </ListItem>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <GroupWork />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeCompaniesItemTwoPrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeCompaniesItemTwoSecondary) }
                    />
                  </ListItem>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <AccountBalanceWallet />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeCompaniesItemThreePrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeCompaniesItemThreeSecondary) }
                    />
                  </ListItem>
                </List>
              </MainList>
            </Grid>
            <Grid item xs={ 12 } sm={ 6 }>
              <ResponsiveImage width='500' src={ companiesImage } />
            </Grid>
          </Grid>

          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 6 }>
              <MainTitle left>
                <Typography variant='h5' gutterBottom>
                  <FormattedMessage id='welcome.headline.collab' defaultMessage='For collaboration' />
                </Typography>
              </MainTitle>
              <MainList>
                <List>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Apps />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeCollabItemOnePrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeCollabItemOneSecondary) }
                    />
                  </ListItem>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Work />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeCollabItemTwoPrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeCollabItemTwoSecondary) }
                    />
                  </ListItem>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <AccountBalanceWallet />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeCollabItemThreePrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeCollabItemThreeSecondary) }
                    />
                  </ListItem>
                </List>
              </MainList>
            </Grid>
            <Grid item xs={ 12 } sm={ 6 }>
              <ResponsiveImage width='600' src={ teamImage } />
            </Grid>
          </Grid>

          <MainTitle>
            <Typography variant='h5' gutterBottom>
              <FormattedMessage id='welcome.tagline.headline.how.title' defaultMessage='How it works' />
            </Typography>
          </MainTitle>
          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 6 }>
              <ResponsiveImage width='400' src={ deal } />
            </Grid>
            <Grid item xs={ 12 } sm={ 6 }>

                <List>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Archive />
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeHowToItemOnePrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeHowToItemOneSecondary) }
                    />
                  </ListItem>
                  <Divider />
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <BugReport />
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeHowToItemTwoPrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeHowToItemTwoSecondary) }
                    />
                  </ListItem>
                  <Divider />
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <CardMembership />
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeHowToItemThreePrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeHowToItemThreeSecondary) }
                    />
                  </ListItem>
                  <Divider />
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <BugReport />
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(messages.welcomeHowToItemFourPrimary) }
                      secondary={ this.props.intl.formatMessage(messages.welcomeHowToItemFourSecondary) }
                    />
                  </ListItem>
                </List>

            </Grid>
          </Grid>

          <Pricing />

          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 4 } className={ classes.alignRight }>

                <Typography variant='h6' gutterBottom>
                  <FormattedMessage id='welcome.integration.title' defaultMessage='Integration' />
                </Typography>
                <Typography variant='h4' gutterBottom>
                  <FormattedMessage id='welcome.integration.subtitle' defaultMessage='Check out our Github app' />
                </Typography>
                <Typography variant='subtitle1' gutterBottom>
                  <FormattedMessage id='welcome.integration.desc' defaultMessage='You can install our Gitpay app on your Github and start to boost your issues' />
                </Typography>
                <Button component='a' target='_blank' href='https://github.com/apps/gitpay-me' variant='contained' color='primary' className={ classes.gutterTopSmall }>
                  <FormattedMessage id='welcome.integration.button' defaultMessage='Checkout our Github App' />
                  <ArrowForward />
                </Button>

            </Grid>
            <Grid item xs={ 12 } sm={ 8 } className={ classes.alignLeft }>
              <ShadowImage width='600' src={ appSnapshotImage } />
            </Grid>
          </Grid>

        <Section ref='get-started' style={ { background: `url(${citySoftware}) no-repeat`, backgroundSize: 'cover', height: 300, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' } }>
          <Typography variant='h6' gutterBottom style={ { padding: '0 60px' } }>
            <FormattedHTMLMessage id='welcome.bottom.call' defaultMessage='A better way to build your project,
 a better way to work in projects' />
          </Typography>
          <Button component='a' href='https://gitpay.me/#/login' size='large' variant='contained' color='primary' className={ classes.gutterTopSmall }>
            <FormattedMessage id='welcome.bottom.link' defaultMessage='Get started' />
          </Button>
          <Button component='a' href='https://docs.gitpay.me' size='large' variant='text' color='primary' className={ classes.gutterTopSmall }>
            <FormattedMessage id='welcome.bottom.linkAlt' defaultMessage='See our docs' />
          </Button>

        <Bottom />

    )
  }
}

Welcome.propTypes = {
  classes: PropTypes.object.isRequired,
  location: PropTypes.object,
}

export default injectIntl(withStyles(styles)(Welcome))
<commit_msg>Fix main navigation to follow the sections as we scroll the page (#334)<commit_after>import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import PropTypes from 'prop-types'
import 'typeface-roboto'
import {
  withStyles,
  Grid,
  Typography,
  Divider,
  List,
  ListItem,
  ListItemText,
  ListItemIcon,
  Avatar,
  Tabs,
  Tab,
  AppBar,
  Button
} from '@material-ui/core'

import {
  AccountBalanceWallet,
  Work,
  Apps,
  Assignment,
  GroupWork,
  Archive,
  CardMembership,
  BugReport,
  ArrowForward
} from '@material-ui/icons'

import scrollToComponent from 'react-scroll-to-component'
import { injectIntl, FormattedMessage, FormattedHTMLMessage } from 'react-intl'

import './mailchimp.css'

import TopBarContainer from '../../containers/topbar'
import InfoContainer from '../../containers/info'
import Bottom from '../../components/bottom/bottom'
import LoginButton from '../../components/session/login-button'
import Pricing from './pricing'
import messages from './messages'
import OurStack from './components/OurStack'
import mainStyles from '../styles/style'
import SubscribeForm from '../form/subscribe-form'

const freelancerImage = require('../../images/welcome-freelancer.png')
const companiesImage = require('../../images/welcome-companies.png')
const teamImage = require('../../images/welcome-teamwork.png')
const appSnapshotImage = require('../../images/gitpay-app.png')
const citySoftware = require('../../images/city-software.png')
const deal = require('../../images/deal.png')

import {
  MainTitle,
  MainList,
  MainBanner,
  ResponsiveImage,
  ShadowImage,
  Section
} from './components/CommonStyles'

const styles = theme => mainStyles(theme)
const OFFSET = -30

class Welcome extends Component {
  constructor (props) {
    super(props)

    this.state = {
      value: 0
    }

    this.handleSectionTab = this.handleSectionTab.bind(this)
  }

  componentDidMount () {
    for (let ref in this.refs) {
      const domNode = ReactDOM.findDOMNode(this.refs[ref])
      const y = domNode.getBoundingClientRect().top
      const position = {
        [ref]: ref === 'integrations' ? y + OFFSET - 20 : y + OFFSET
      }
      this.positions = { ...this.positions, ...position }
    }

    window.addEventListener('scroll', this.handleSectionsScroll)
  }

  componentWillUnmount () {
    window.removeEventListener('scroll', this.handleSectionsScroll)
  }

  handleSectionTab = (event, value) => {
    this.setState({ value })

    scrollToComponent(this.refs[event.currentTarget.id], {
      offset: event.currentTarget.id === 'integrations' ? OFFSET - 20 : OFFSET,
      align: 'top',
      ease: 'inExpo'
    })
  }

  handleSectionsScroll = ({ pageY }) => {
    const {
      // intro,
      contrib,
      companies,
      collab,
      'how-it-works': howItWorks,
      pricing,
      integrations,
      'get-started': getStarted
    } = this.positions

    if (pageY < contrib) {
      this.setState({ value: 0 })
    }
    else if (pageY < companies) {
      this.setState({ value: 1 })
    }
    else if (pageY < collab) {
      this.setState({ value: 2 })
    }
    else if (pageY < howItWorks) {
      this.setState({ value: 3 })
    }
    else if (pageY < pricing) {
      this.setState({ value: 4 })
    }
    else if (pageY < integrations) {
      this.setState({ value: 5 })
    }
    else if (pageY < getStarted) {
      this.setState({ value: 6 })
    }
    else this.setState({ value: 7 })
  }

  render () {
    const { classes, location } = this.props

    return (

        <TopBarContainer ref='intro' />
        <AppBar position='sticky' color='default'>
          <Tabs
            variant='fullWidth'
            value={ this.state.value }
            onChange={ this.handleSectionTab }
          >
            <Tab
              id='intro'
              value={ 0 }
              label={ this.props.intl.formatMessage(messages.topMenu1) }
            />
            <Tab
              id='contrib'
              value={ 1 }
              label={ this.props.intl.formatMessage(messages.topMenu2) }
            />
            <Tab
              id='companies'
              value={ 2 }
              label={ this.props.intl.formatMessage(messages.topMenu3) }
            />
            <Tab
              id='collab'
              value={ 3 }
              label={ this.props.intl.formatMessage(messages.topMenu4) }
            />
            <Tab
              id='how-it-works'
              value={ 4 }
              label={ this.props.intl.formatMessage(messages.topMenu5) }
            />
            <Tab
              id='pricing'
              value={ 5 }
              label={ this.props.intl.formatMessage(messages.topMenu6) }
            />
            <Tab
              id='integrations'
              value={ 6 }
              label={ this.props.intl.formatMessage(messages.topMenu7) }
            />
            <Tab
              id='get-started'
              value={ 7 }
              label={ this.props.intl.formatMessage(messages.topMenu8) }
            />
          </Tabs>
        </AppBar>
        <MainBanner>
          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } style={ { padding: 0, margin: 0 } }>

                <Typography className={ classes.tagline } gutterBottom>
                  <FormattedMessage
                    id='welcome.tagline'
                    defaultMessage='Welcome to Gitpay'
                  />
                </Typography>
                <Typography variant='h6' gutterBottom>
                  <FormattedMessage
                    id='welcome.tagline1'
                    defaultMessage='Work in tasks on demand'
                  />
                </Typography>
                <Typography type='subtitle1' gutterBottom noWrap>
                  <FormattedHTMLMessage
                    id='welcome.tagline2'
                    defaultMessage='and receive bounty for your contributions'
                  />
                </Typography>

                  <SubscribeForm type='subscribe-form-main' />

                <LoginButton referer={ location } contrast includeForm={ false } />

              <InfoContainer />
              <OurStack />
            </Grid>
          </Grid>
        </MainBanner>

          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 6 }>
              <MainTitle left>
                <Typography variant='h5' gutterBottom>
                  <FormattedMessage
                    id='welcome.headline.forfreelancers'
                    defaultMessage='For freelancers'
                  />
                </Typography>
              </MainTitle>
              <MainList>
                <List>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Apps />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeFreelancersItemOnePrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeFreelancersItemOneSecondary
                      ) }
                    />
                  </ListItem>

                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Work />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeFreelancersItemTwoPrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeFreelancersItemTwoSecondary
                      ) }
                    />
                  </ListItem>

                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <AccountBalanceWallet />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeFreelancersItemThreePrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeFreelancersItemThreeSecondary
                      ) }
                    />
                  </ListItem>
                </List>
              </MainList>
            </Grid>
            <Grid item xs={ 12 } sm={ 6 }>
              <ResponsiveImage width='600' src={ freelancerImage } />
            </Grid>
          </Grid>

          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 6 }>
              <MainTitle left>
                <Typography variant='h5' gutterBottom>
                  <FormattedMessage
                    id='welcome.tagline.companies.main.headline'
                    defaultMessage='For companies'
                  />
                </Typography>
              </MainTitle>
              <MainList>
                <List>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Assignment />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeCompaniesItemOnePrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeCompaniesItemOneSecondary
                      ) }
                    />
                  </ListItem>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <GroupWork />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeCompaniesItemTwoPrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeCompaniesItemTwoSecondary
                      ) }
                    />
                  </ListItem>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <AccountBalanceWallet />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeCompaniesItemThreePrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeCompaniesItemThreeSecondary
                      ) }
                    />
                  </ListItem>
                </List>
              </MainList>
            </Grid>
            <Grid item xs={ 12 } sm={ 6 }>
              <ResponsiveImage width='500' src={ companiesImage } />
            </Grid>
          </Grid>

          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 6 }>
              <MainTitle left>
                <Typography variant='h5' gutterBottom>
                  <FormattedMessage
                    id='welcome.headline.collab'
                    defaultMessage='For collaboration'
                  />
                </Typography>
              </MainTitle>
              <MainList>
                <List>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Apps />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeCollabItemOnePrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeCollabItemOneSecondary
                      ) }
                    />
                  </ListItem>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <Work />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeCollabItemTwoPrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeCollabItemTwoSecondary
                      ) }
                    />
                  </ListItem>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Avatar className={ classes.iconFill }>
                        <AccountBalanceWallet />
                      </Avatar>
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeCollabItemThreePrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeCollabItemThreeSecondary
                      ) }
                    />
                  </ListItem>
                </List>
              </MainList>
            </Grid>
            <Grid item xs={ 12 } sm={ 6 }>
              <ResponsiveImage width='600' src={ teamImage } />
            </Grid>
          </Grid>

          <MainTitle>
            <Typography variant='h5' gutterBottom>
              <FormattedMessage
                id='welcome.tagline.headline.how.title'
                defaultMessage='How it works'
              />
            </Typography>
          </MainTitle>
          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 6 }>
              <ResponsiveImage width='400' src={ deal } />
            </Grid>
            <Grid item xs={ 12 } sm={ 6 }>

                <List>
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <Archive />
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeHowToItemOnePrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeHowToItemOneSecondary
                      ) }
                    />
                  </ListItem>
                  <Divider />
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <BugReport />
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeHowToItemTwoPrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeHowToItemTwoSecondary
                      ) }
                    />
                  </ListItem>
                  <Divider />
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <CardMembership />
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeHowToItemThreePrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeHowToItemThreeSecondary
                      ) }
                    />
                  </ListItem>
                  <Divider />
                  <ListItem className={ classes.listIconTop }>
                    <ListItemIcon>
                      <BugReport />
                    </ListItemIcon>
                    <ListItemText
                      primary={ this.props.intl.formatMessage(
                        messages.welcomeHowToItemFourPrimary
                      ) }
                      secondary={ this.props.intl.formatMessage(
                        messages.welcomeHowToItemFourSecondary
                      ) }
                    />
                  </ListItem>
                </List>

            </Grid>
          </Grid>

          <Pricing />

          <Grid container spacing={ 24 }>
            <Grid item xs={ 12 } sm={ 4 } className={ classes.alignRight }>

                <Typography variant='h6' gutterBottom>
                  <FormattedMessage
                    id='welcome.integration.title'
                    defaultMessage='Integration'
                  />
                </Typography>
                <Typography variant='h4' gutterBottom>
                  <FormattedMessage
                    id='welcome.integration.subtitle'
                    defaultMessage='Check out our Github app'
                  />
                </Typography>
                <Typography variant='subtitle1' gutterBottom>
                  <FormattedMessage
                    id='welcome.integration.desc'
                    defaultMessage='You can install our Gitpay app on your Github and start to boost your issues'
                  />
                </Typography>
                <Button
                  component='a'
                  target='_blank'
                  href='https://github.com/apps/gitpay-me'
                  variant='contained'
                  color='primary'
                  className={ classes.gutterTopSmall }
                >
                  <FormattedMessage
                    id='welcome.integration.button'
                    defaultMessage='Checkout our Github App'
                  />
                  <ArrowForward />
                </Button>

            </Grid>
            <Grid item xs={ 12 } sm={ 8 } className={ classes.alignLeft }>
              <ShadowImage width='600' src={ appSnapshotImage } />
            </Grid>
          </Grid>

        <Section
          ref='get-started'
          style={ {
            background: `url(${citySoftware}) no-repeat`,
            backgroundSize: 'cover',
            height: 300,
            display: 'flex',
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center'
          } }
        >
          <Typography variant='h6' gutterBottom style={ { padding: '0 60px' } }>
            <FormattedHTMLMessage
              id='welcome.bottom.call'
              defaultMessage='A better way to build your project,
 a better way to work in projects'
            />
          </Typography>
          <Button
            component='a'
            href='https://gitpay.me/#/login'
            size='large'
            variant='contained'
            color='primary'
            className={ classes.gutterTopSmall }
          >
            <FormattedMessage
              id='welcome.bottom.link'
              defaultMessage='Get started'
            />
          </Button>
          <Button
            component='a'
            href='https://docs.gitpay.me'
            size='large'
            variant='text'
            color='primary'
            className={ classes.gutterTopSmall }
          >
            <FormattedMessage
              id='welcome.bottom.linkAlt'
              defaultMessage='See our docs'
            />
          </Button>

        <Bottom />

    )
  }
}

Welcome.propTypes = {
  classes: PropTypes.object.isRequired,
  location: PropTypes.object
}

export default injectIntl(withStyles(styles)(Welcome))
<|endoftext|>


<commit_before>/*
 *  NOTE: In case the version number doesn't tip you off... this is really
 *  preliminary code. I welcome you to try it out and tell me if it sucks;
 *  I'll try to make it suck less. Thanks!
 *
 *  Prototype History Manager
 *  =========================
 *
 *  A port of YUI's Browser History Manager with a slightly different API.
 *
 *  INSTRUCTIONS:
 *  First: to get this working across all browsers you need both a hidden
 *  INPUT field _and_ a hidden IFRAME. Somewhere on your page (preferably
 *  at the bottom) you'll need to tell the history manager where these things
 *  are. For example:
 *
 *  <input type="hidden" id="history_field" />
 *
 *
 *
 *  The `History` object functions as an instance of Hash. While the page is
 *  loading, any calls to `History.set` will determine the _initial_ value
 *  of any key-value pair. After the window.load event fires, any key/value
 *  pairs are serialized and placed into the URL hash.
 *
 *  Now, whenever `History.set` is called, the URL hash will change, and the
 *  browser will treat that change as a "page navigation."
 *
 *  Whenever the value of a key changes, whether by updating the value through
 *  `History.set`, using Back/Forward, or hacking the URL, the history manager
 *  will fire a custom event for each key that changed, in the form of
 *  `hash:changed:[name_of_key]`. The event object's `memo` property will
 *  have several properties: `initialState`, `previousState`, and
 *  `currentState`, informing you what the value used to be and what it is now.
 *
 *
 *  DETAILED EXAMPLE:
 *
 *  (during page load)
 *  History.set("foo", "bar");
 *  History.set("baz", "thud");
 *
 *  (when page loads)
 *  URL hash set to "#foo=bar&baz=thud"
 *
 *  (later...)
 *  History.set("foo", "blerg");
 *  URL hash set to "#foo=blerg&baz=thud"
 *  `document` fires custom event: `hash:changed:foo`
 *    event.memo.initialState:  "bar"
 *    event.memo.previousState: "bar"
 *    event.memo.currentState:  "blerg"
 *
 *  (later still...)
 *  User clicks on Back button
 *  URL hash set to "#foo=bar&baz=thud"
 *  `document` fires custom event: `hash:changed:foo`
 *    event.memo.initialState:  "bar"
 *    event.memo.previousState: "blerg"
 *    event.memo.currentState:  "bar"
 *
 */

(function() {
  var VERSION = "0.1";

  var _iframe = null;
  var _stateField = null;

  var _initialized = false;

  var _uniqueStates = [];

  var _originalHash = null;
  var _historyHash = null;

  var DEBUG = false;

  function _debug() {
    if (!DEBUG) return;
    return console.log(arguments);
  }

  function _initialize() {
    _stateField = History.stateField;
    _iframe     = History.iframe;
    _debug("in _initialize");

    // Unserialize the hash from the hidden field.
    _historyHash = new $HistoryHash(_stateField.value);

    if (Prototype.Browser.IE) {
      _checkIFrameLoaded();
    } else {
      // Poll for hash changes.
      var counter = history.length, hash = _getHash();

      window.setInterval( function() {
        var newHash = _getHash(), newCounter = history.length;

        if (newHash !== hash) {
          hash = newHash;
          _historyHash.updateWithURLHash(newHash);
          _storeStates();
        } else if (newCounter !== counter && Prototype.Browser.WebKit) {
          hash = newHash, counter = newCounter;
          var uniqueState = _uniqueStates[counter - 1];
          _historyHash.updateWithURLHash(uniqueState);
          _storeStates();
        }
      }, 50);

      // At this point, window.History is an ordinary hash. Extract its
      // values, then replace it with a $HistoryHash instance.
      _originalHash = window.History;
      _historyHash.update(_originalHash.toObject());

      console.log(_originalHash);
      window._old = _originalHash;

      window.History = _historyHash;
      window.History.VERSION = VERSION;

      _initialized = true;
    }
  }

  function _getHash() {
    var href = top.location.href, i = href.indexOf("#");
    return i >= 0 ? href.substr(i + 1) : null;
  }

  function _setHash(hash) {
    top.location.hash = hash;
  }

  function _updateHash(historyHash) {
    _setHash(historyHash.toURLHash());
  }

  function  _storeStates() {
    var initialStates = [], currentStates = [];

    _historyHash.each( function(pair) {
      initialStates.push([pair.key, pair.value.initialState].join('='));
      currentStates.push([pair.key, pair.value.currentState].join('='));
    });

    _stateField.value = initialStates.join("&") + "|" + currentStates.join("&");

    if (Prototype.Browser.WebKit) {
      _stateField.value += "|" + _uniqueStates.join(",");
    }
  }

  function _updateIFrame(uniqueState) {
    var html = '
' + uniqueState +
     '
';

    try {
      var doc = _iframe.contentWindow.document;
      doc.open();
      doc.write(html);
      doc.close();
    } catch (e) {
      return false;
    }
  }

  function _getUniqueStateForIE() {
    var doc = _iframe.contentWindow.document;
    var elem = doc.getElementById("state");

    return elem ? elem.innerText : null;
  }

  function _checkIFrameLoaded() {
    if (!_iframe.contentWindow || _iframe.contentWindow.document) {
      // Not ready yet. Keep trying.
      window.setTimeout(_checkIFrameLoaded, 10);
      return;
    }

    var uniqueState = _getUniqueStateForIE();
    var hash = _getHash();

    // Keep polling to see if the values we have above are no longer
    // current. If so, fire off the observers.
    window.setInterval( function() {
      var newUniqueState = _getUniqueStateForIE();
      var newHash = _getHash();

      if (newUniqueState !== uniqueState) {
        uniqueState = newUniqueState;
        _historyHash.updateWithURLHash(uniqueState);
        hash = newHash;
        _setHash(hash);
        _storeStates();
      } else if (newHash !== hash) {
        // Hash changed on its own.
        hash = newHash;
        _updateIFrame(newHash);
        _historyHash.updateWithURLHash(hash);
      }
    }, 50);

    _initialized = true;
  }

  var $HistoryHash = Class.create(Hash, {
    initialize: function(uniqueState) {
      this.uniqueState = uniqueState;
      this._object = {};

      var parts = uniqueState.split("|");

      var initialStates = {};
      var currentStates = {};

      if (parts.length >= 2) {
        var iParts = parts[0].split("&");
        iParts.each( function(part) {
          var pair = part.split("=");
          initialStates[pair[0]] = pair[1];
        });
        var cParts = parts[1].split("&");
        cParts.each( function(part) {
          var pair = part.split("=");
          currentStates[pair[0]] = pair[1];
        });
      }

      this._object = {};

      Object.keys(currentStates).each( function(state) {
        this._object[state] = {
          initialState: initialStates[state],
          currentState: currentStates[state]
        };
      }, this);
    },

    updateWithURLHash: function(hash) {
      if (!hash) {
        return;
      }
      var pairs = hash.split('&');

      pairs.each( function(p) {
        pair = p.split('=');
        this.set(pair[0], pair[1]);
      }, this);
    },

    set: function($super, key, value) {
      var item;
      if (!this.get(key)) {
        if (_initialized) {
          throw new Error("New key cannot be set after initialization!");
        } else {
          item = { initialState: value };
        }
      } else {
        item = this._object[key];
      }
      if (item.currentState && item.currentState !== value) {
        _debug("in set", arguments);
        // Value has changed. Fire an event.
        var memo = Object.clone(item);
        memo.previousState = item.currentState;
        memo.currentState  = value;
        document.fire("hash:changed:" + key, memo);
      }
      item.currentState = value;
      item = $super(key, item);
      _updateHash(this);
      return item;
    },

    get: function($super, key) {
      var value = $super(key);
      return value ? value.currentState : null;
    },

    toURLHash: function() {
      function toURLPair(pair) {
        return [pair.key, pair.value.currentState].join('=');
      }
      return "#" + this.map(toURLPair).join('&');
    }
  });

  Event.observe(window, 'load', _initialize);

  window.History = new Hash();
  window.History.VERSION = VERSION;
})();<commit_msg>Added PDoc for history.js.<commit_after>/**
 *  NOTE: In case the version number doesn't tip you off... this is really
 *  preliminary code. I welcome you to try it out and tell me if it sucks;
 *  I'll try to make it suck less. Thanks!
 *
 *  Prototype History Manager
 *  =========================
 *
 *  A port of YUI's Browser History Manager with a slightly different API.
 *
 *  INSTRUCTIONS:
 *  First: to get this working across all browsers you need both a hidden
 *  INPUT field _and_ a hidden IFRAME. Somewhere on your page (preferably
 *  at the bottom) you'll need to tell the history manager where these things
 *  are. For example:
 *
 *  <input type="hidden" id="history_field" />
 *
 *
 *
 *  The `History` object functions as an instance of Hash. While the page is
 *  loading, any calls to `History.set` will determine the _initial_ value
 *  of any key-value pair. After the window load event fires, any key/value
 *  pairs are serialized and placed into the URL hash.
 *
 *  Now, whenever `History.set` is called, the URL hash will change, and the
 *  browser will treat that change as a "page navigation."
 *
 *  Whenever the value of a key changes, whether by updating the value through
 *  `History.set`, using Back/Forward, or hacking the URL, the history manager
 *  will fire a custom event for each key that changed, in the form of
 *  `hash:changed:[name_of_key]`. The event object's `memo` property will
 *  have several properties: `initialState`, `previousState`, and
 *  `currentState`, informing you what the value used to be and what it is now.
 *
 *
 *  DETAILED EXAMPLE:
 *
 *  (during page load)
 *  History.set("foo", "bar");
 *  History.set("baz", "thud");
 *
 *  (when page loads)
 *  URL hash set to "#foo=bar&baz=thud"
 *
 *  (later...)
 *  History.set("foo", "blerg");
 *  URL hash set to "#foo=blerg&baz=thud"
 *  `document` fires custom event: `hash:changed:foo`
 *    event.memo.initialState:  "bar"
 *    event.memo.previousState: "bar"
 *    event.memo.currentState:  "blerg"
 *
 *  (later still...)
 *  User clicks on Back button
 *  URL hash set to "#foo=bar&baz=thud"
 *  `document` fires custom event: `hash:changed:foo`
 *    event.memo.initialState:  "bar"
 *    event.memo.previousState: "blerg"
 *    event.memo.currentState:  "bar"
 *
 */

(function() {
  var VERSION = "0.1";

  var _iframe = null;
  var _stateField = null;

  var _initialized = false;

  var _uniqueStates = [];

  var _originalHash = null;
  var _historyHash = null;

  var DEBUG = false;

  function _debug() {
    if (!DEBUG) return;
    return console.log(arguments);
  }

  function _initialize() {
    _stateField = History.stateField;
    _iframe     = History.iframe;
    _debug("in _initialize");

    // Unserialize the hash from the hidden field.
    _historyHash = new $HistoryHash(_stateField.value);

    if (Prototype.Browser.IE) {
      _checkIFrameLoaded();
    } else {
      // Poll for hash changes.
      var counter = history.length, hash = _getHash();

      window.setInterval( function() {
        var newHash = _getHash(), newCounter = history.length;

        if (newHash !== hash) {
          hash = newHash;
          _historyHash.updateWithURLHash(newHash);
          _storeStates();
        } else if (newCounter !== counter && Prototype.Browser.WebKit) {
          hash = newHash, counter = newCounter;
          var uniqueState = _uniqueStates[counter - 1];
          _historyHash.updateWithURLHash(uniqueState);
          _storeStates();
        }
      }, 50);

      // At this point, window.History is an ordinary hash. Extract its
      // values, then replace it with a $HistoryHash instance.
      _originalHash = window.History;
      _historyHash.update(_originalHash.toObject());

      console.log(_originalHash);
      window._old = _originalHash;

      window.History = _historyHash;
      window.History.VERSION = VERSION;

      _initialized = true;
    }
  }

  function _getHash() {
    var href = top.location.href, i = href.indexOf("#");
    return i >= 0 ? href.substr(i + 1) : null;
  }

  function _setHash(hash) {
    top.location.hash = hash;
  }

  function _updateHash(historyHash) {
    _setHash(historyHash.toURLHash());
  }

  function  _storeStates() {
    var initialStates = [], currentStates = [];

    _historyHash.each( function(pair) {
      initialStates.push([pair.key, pair.value.initialState].join('='));
      currentStates.push([pair.key, pair.value.currentState].join('='));
    });

    _stateField.value = initialStates.join("&") + "|" + currentStates.join("&");

    if (Prototype.Browser.WebKit) {
      _stateField.value += "|" + _uniqueStates.join(",");
    }
  }

  function _updateIFrame(uniqueState) {
    var html = '
' + uniqueState +
     '
';

    try {
      var doc = _iframe.contentWindow.document;
      doc.open();
      doc.write(html);
      doc.close();
    } catch (e) {
      return false;
    }
  }

  function _getUniqueStateForIE() {
    var doc = _iframe.contentWindow.document;
    var elem = doc.getElementById("state");

    return elem ? elem.innerText : null;
  }

  function _checkIFrameLoaded() {
    if (!_iframe.contentWindow || _iframe.contentWindow.document) {
      // Not ready yet. Keep trying.
      window.setTimeout(_checkIFrameLoaded, 10);
      return;
    }

    var uniqueState = _getUniqueStateForIE();
    var hash = _getHash();

    // Keep polling to see if the values we have above are no longer
    // current. If so, fire off the observers.
    window.setInterval( function() {
      var newUniqueState = _getUniqueStateForIE();
      var newHash = _getHash();

      if (newUniqueState !== uniqueState) {
        uniqueState = newUniqueState;
        _historyHash.updateWithURLHash(uniqueState);
        hash = newHash;
        _setHash(hash);
        _storeStates();
      } else if (newHash !== hash) {
        // Hash changed on its own.
        hash = newHash;
        _updateIFrame(newHash);
        _historyHash.updateWithURLHash(hash);
      }
    }, 50);

    _initialized = true;
  }

  var $HistoryHash = Class.create(Hash, {
    initialize: function(uniqueState) {
      this.uniqueState = uniqueState;
      this._object = {};

      var parts = uniqueState.split("|");

      var initialStates = {};
      var currentStates = {};

      if (parts.length >= 2) {
        var iParts = parts[0].split("&");
        iParts.each( function(part) {
          var pair = part.split("=");
          initialStates[pair[0]] = pair[1];
        });
        var cParts = parts[1].split("&");
        cParts.each( function(part) {
          var pair = part.split("=");
          currentStates[pair[0]] = pair[1];
        });
      }

      this._object = {};

      Object.keys(currentStates).each( function(state) {
        this._object[state] = {
          initialState: initialStates[state],
          currentState: currentStates[state]
        };
      }, this);
    },

    updateWithURLHash: function(hash) {
      if (!hash) {
        return;
      }
      var pairs = hash.split('&');

      pairs.each( function(p) {
        pair = p.split('=');
        this.set(pair[0], pair[1]);
      }, this);
    },

    /**
     *  History.set(key, value)
     *
     *  Sets a key/value pair, updating the URL and triggering a new
     *  navigation state in the browser.
     *  - key (String): The key to set.
     *  - value (String): The value to set.
    **/
    set: function($super, key, value) {
      var item;
      if (!this.get(key)) {
        if (_initialized) {
          throw new Error("New key cannot be set after initialization!");
        } else {
          item = { initialState: value };
        }
      } else {
        item = this._object[key];
      }
      if (item.currentState && item.currentState !== value) {
        _debug("in set", arguments);
        // Value has changed. Fire an event.
        var memo = Object.clone(item);
        memo.previousState = item.currentState;
        memo.currentState  = value;
        document.fire("hash:changed:" + key, memo);
      }
      item.currentState = value;
      item = $super(key, item);
      _updateHash(this);
      return item;
    },

    /**
     *  Hash.get(key)
     *  Retrieves the _current_ value of the item in the URL hash with the
     *  given key.
     *  - key (String): The key to retrieve.
    **/
    get: function($super, key) {
      var value = $super(key);
      return value ? value.currentState : null;
    },

    toURLHash: function() {
      function toURLPair(pair) {
        return [pair.key, pair.value.currentState].join('=');
      }
      return "#" + this.map(toURLPair).join('&');
    }
  });

  Event.observe(window, 'load', _initialize);

  window.History = new Hash();
  window.History.VERSION = VERSION;
})();<|endoftext|>