<commit_before>var PANO = {};
PANO.main = function() {

var camera, scene, renderer, tween;

var fov = 70,
texture_placeholder,
isUserInteracting = false,
onMouseDownMouseX = 0, onMouseDownMouseY = 0,
lon = 0, onMouseDownLon = 0,
lat = 0, onMouseDownLat = 0,
phi = 0, theta = 0;

var orbs = [];
var currentOrb = 0;
var audio;
var loading = 0;
var soundFiles = [];
var soundPos = [];

// Load configuration
PANO.sounds.forEach(function(s) {
	if (s.length < 2)
		return console.error("Invalid config", s);
	soundFiles.push(s[0]);
	soundPos.push(s[1]);
});
if (PANO.helper) {
	document.write('

');
}

var loadingDone = soundFiles.length;

initAura();
initScene();
initOrbs();
initUI();

animate(); // go!

function initAura() {
	var a = {};
	audio = a;

	// Detect if the audio context is supported.
	window.AudioContext = (
	  window.AudioContext ||
	  window.webkitAudioContext ||
	  null
	);
	if (!AudioContext) {
	  throw new Error("AudioContext not supported!");
	}

	a.context = new AudioContext();
	a.convolver = a.context.createConvolver();
	a.volume = a.context.createGain();

	a.mixer = a.context.createGain();

	a.flatGain = a.context.createGain();
	a.convolverGain = a.context.createGain();

	a.destination = a.mixer;
	a.mixer.connect(a.flatGain);
	//a.mixer.connect(a.convolver);
	a.convolver.connect(a.convolverGain);
	a.flatGain.connect(a.volume);
	a.convolverGain.connect(a.volume);
	a.volume.connect(a.context.destination);
}

function loadBuffer(soundFileName, callback) {
	var request = new XMLHttpRequest();
	request.open("GET", soundFileName, true);
	request.responseType = "arraybuffer";
	var ctx = audio.context;
	request.onload = function() {
	  ctx.decodeAudioData(request.response, function onSuccess(decodedBuffer) {
		callback(decodedBuffer);
	  }, function onFailure() {
		alert("Decoding the audio buffer failed");
	  });
	};
	request.send();
	return request;
}

function loadSound(soundFileName) {
	var ctx = audio.context;

	var sound = {};
	sound.source = ctx.createBufferSource();
	sound.source.loop = true;
	sound.panner = ctx.createPanner();
	sound.volume = ctx.createGain();

	sound.source.connect(sound.volume);
	sound.volume.connect(sound.panner);
	sound.panner.connect(audio.destination);

	setTimeout(function(){
		loadBuffer(soundFileName,
			function(buffer){
			  sound.buffer = buffer;
			  sound.source.buffer = sound.buffer;
			  loadProgress(1);
			});
	}, 250);

	return sound;
}

function loadProgress(dir) {
	loading += dir;
	var pc = parseInt(100 * (loading+1) / loadingDone) + '%';
	document.getElementById('pc').style.width = pc;
	document.getElementById('pctx').innerHTML = 'loading ' + pc;
}

function setPositionAndVelocity(object, audioNode, x, y, z, dt) {

	//var p = object.matrixWorld.getPosition();
	var p = new THREE.Vector3()
			.getPositionFromMatrix(object.matrixWorld);
	var px = p.x, py = p.y, pz = p.z;
	object.position.set(x,y,z);
	object.updateMatrixWorld();
	//var q = object.matrixWorld.getPosition();
	var q = new THREE.Vector3()
			.getPositionFromMatrix(object.matrixWorld);
	var dx = q.x-px, dy = q.y-py, dz = q.z-pz;
	audioNode.setPosition(q.x, q.y, q.z);
}

function setPosition(object, x, y, z, dt) {

	setPositionAndVelocity(object, object.sound.panner, x, y, z, dt);
	var vec = new THREE.Vector3(0,0,1);
	var m = object.matrixWorld;
	var mx = m.n14, my = m.n24, mz = m.n34;
	m.n14 = m.n24 = m.n34 = 0;
	vec.applyMatrix3(m);
	//m.multiplyVector3(vec);
	vec.normalize();
	object.sound.panner.setOrientation(vec.x, vec.y, vec.z);
	m.n14 = mx;
	m.n24 = my;
	m.n34 = mz;
}

function setListenerPosition(object, x, y, z, dt) {

	setPositionAndVelocity(object, audio.context.listener, x, y, z, dt);

	var m = object.matrix;
	var mx = m.n14, my = m.n24, mz = m.n34;
	m.n14 = m.n24 = m.n34 = 0;

	var vec = new THREE.Vector3(0,0,1);
	vec.applyMatrix3(m);
	//m.multiplyVector3(vec);
	vec.normalize();

	var up = new THREE.Vector3(0,-1,0);
	up.applyMatrix3(m);
	//m.multiplyVector3(up);
	up.normalize();

	audio.context.listener.setOrientation(vec.x, vec.y, vec.z, up.x, up.y, up.z);

	m.n14 = mx;
	m.n24 = my;
	m.n34 = mz;
}

function initScene() {

	var container, mesh;

	container = document.getElementById( 'container' );

	camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 1, 1100 );
	camera.target = new THREE.Vector3( 0, 0, 0 );

	scene = new THREE.Scene();

	var geometry = new THREE.SphereGeometry( 500, 60, 40 );
	geometry.applyMatrix( new THREE.Matrix4().makeScale( -1, 1, 1 ) );

	var material = new THREE.MeshBasicMaterial( {
		map: THREE.ImageUtils.loadTexture( PANO.panorama )
	} );

	mesh = new THREE.Mesh( geometry, material );
	scene.add( mesh );

	renderer = new THREE.WebGLRenderer();
	renderer.setSize( window.innerWidth, window.innerHeight );
	container.appendChild( renderer.domElement );

	document.addEventListener( 'mousedown', onDocumentMouseDown, false );
	document.addEventListener( 'mousemove', onDocumentMouseMove, false );
	document.addEventListener( 'mouseup', onDocumentMouseUp, false );
	document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
	document.addEventListener( 'DOMMouseScroll', onDocumentMouseWheel, false);

	window.addEventListener( 'resize', onWindowResize, false );
} // -initScene

function onWindowResize() {
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();

	renderer.setSize( window.innerWidth, window.innerHeight );
}

function onDocumentMouseDown( event ) {
	event.preventDefault();

	isUserInteracting = true;

	onPointerDownPointerX = event.clientX;
	onPointerDownPointerY = event.clientY;

	onPointerDownLon = lon;
	onPointerDownLat = lat;
}

function onDocumentMouseMove( event ) {
	if ( isUserInteracting ) {
		lon = ( onPointerDownPointerX - event.clientX ) * 0.1 + onPointerDownLon;
		lat = ( event.clientY - onPointerDownPointerY ) * 0.1 + onPointerDownLat;
	}
}

function onDocumentMouseUp( event ) {
	isUserInteracting = false;
}

function onDocumentMouseWheel( event ) {
	// WebKit
	if ( event.wheelDeltaY ) {
		fov -= event.wheelDeltaY * 0.05;

	// Opera / Explorer 9
	} else if ( event.wheelDelta ) {
		fov -= event.wheelDelta * 0.05;

	// Firefox
	} else if ( event.detail ) {
		fov += event.detail * 1.0;
	}

	// Set bounds for zooming
	fov = Math.min(Math.max(fov, 20), 70);

	camera.projectionMatrix.makePerspective( fov, window.innerWidth / window.innerHeight, 1, 1100 );
	render();
}

function animate() {

	requestAnimationFrame(animate);

	if (loading < loadingDone) { return; }
	else if (loading == loadingDone) { // run once
		document.getElementById('loading').style.display = 'none';
		document.getElementById('controls').classList.remove('hide');
		var ctx = audio.context;
		orbs.forEach(function(c) {
			c.sound.source.start(ctx.currentTime + 0.020);
		});
		loading++;
	}

	TWEEN.update();
	updateAura();
	render();
}

function render() {

	lat = Math.max( - 85, Math.min( 85, lat ) );
	phi = THREE.Math.degToRad( 90 - lat );
	theta = THREE.Math.degToRad( lon );

	camera.target.x = 500 * Math.sin( phi ) * Math.cos( theta );
	camera.target.y = 500 * Math.cos( phi );
	camera.target.z = 500 * Math.sin( phi ) * Math.sin( theta );

	camera.lookAt( camera.target );

	/*
	// distortion
	camera.position.x = - camera.target.x;
	camera.position.y = - camera.target.y;
	camera.position.z = - camera.target.z;
	*/

	renderer.render( scene, camera );

}

function initOrbs() {

	var signs = [];

	var resolution = soundFiles.length;
	var amplitude = 20;
	var size = 360 / resolution;

	for (var i = 0; i < resolution; i++) {

		var segment = ( i*size ) * Math.PI/180;
		if (soundPos.length > i) {
			segment = soundPos[i] * Math.PI/180;
		}
		//console.log(i, segment * 180 / Math.PI);

		var X = Math.cos( segment ) * amplitude;
		var Z = Math.sin( segment ) * amplitude;

		var Y = 0;
		if (PANO.sounds[i].length > 2) {
			Y = PANO.sounds[i][2];
		}

		var cube = new THREE.Mesh(
				new THREE.CubeGeometry( 1, 1, 1 ),
				new THREE.MeshBasicMaterial( { color: 0xff0000 } )
			);
		cube.visible = false;
		cube.sound = loadSound('sound/' + soundFiles[i]);
		cube.soundFile = soundFiles[i].replace('.mp3', '');
		scene.add( cube );
		orbs.push(cube);

		var sign = new THREE.Mesh(
				new THREE.SphereGeometry( 5, 10, 10 ),
				new THREE.MeshBasicMaterial( { color: 0x0000ff, transparent: true, opacity: 0.4 } )
			);

		sign.visible = PANO.helper;
		sign.position.set(X, Y, Z);
		cube.origin = sign.position;
		cube.orient = segment;
		cube.orientdeg = segment * 180/Math.PI;

		scene.add( sign );
		signs.push(sign);
	}

} // -initOrbs

function updateAura() {

	var cp = camera.position;
	var camZ = cp.z, camX = cp.x, camY = cp.y;
	setListenerPosition(camera, camX, camY, camZ, 0);

	var cx = camX, cy = camY, cz = camZ;

	var cl = orbs.length;

	var degabs = (theta * 180/Math.PI) % 360;
	degabs = (degabs < 0) ? 360 + degabs : degabs;

	var maxdist = 0.5 * 360/cl;

	// Oscillating function
	orbs.forEach(function(c, i) {
		var dist = Math.abs(c.orientdeg - degabs);
		if (i == 0 && degabs > 180)
			dist = Math.abs(360 - degabs);

		if (dist > maxdist) {
			setPosition(c, 99999, 0, 0, 0);
		} else {
			var offset = Math.pow(( theta - c.orient )*5,3);
			setPosition(c, cx, cy, cz + offset, 0);
			currentOrb = i;

			// For debugging
			if (PANO.helper) {
				document.title = c.soundFile + " ~" + parseInt(degabs);
			}
		}
	});

} // -updateAura

function swingCam(a, b) {

	//lon = soundPos[b]; // testing
	var from = soundPos[a],
		to = soundPos[b];

	// Edges
	if (a == 0 && b > 1) {
		from = 360;
	} else if (b == 0 && a > 1) {
		to = 360;
	}

	// Start animation
	tween = new TWEEN
		.Tween({ lon: from })
		.to({ lon: to }, 2000 )
		.easing( TWEEN.Easing.Cubic.Out )
		.onUpdate(function () {
			lon = this.lon;
		}).start();

} // -swingTo

function initUI() {

	document.getElementById('next').onclick = function() {
		var from = currentOrb;
		currentOrb = (++currentOrb == orbs.length) ? 0 : currentOrb;
		swingCam(from, currentOrb);
	};

	document.getElementById('prev').onclick = function() {
		var from = currentOrb;
		currentOrb = (--currentOrb == -1) ? orbs.length-1 : currentOrb;
		swingCam(from, currentOrb);
	};

} // -initUI

}; // PANO.main<commit_msg>Initial click code<commit_after>var PANO = {};
PANO.main = function() {

var camera, scene, renderer, tween;

var fov = 70,
texture_placeholder,
isUserInteracting = false,
onMouseDownMouseX = 0, onMouseDownMouseY = 0,
lon = 0, onMouseDownLon = 0,
lat = 0, onMouseDownLat = 0,
phi = 0, theta = 0;

var orbs = [];
var currentOrb = 0;
var audio;
var loading = 0;
var soundFiles = [];
var soundPos = [];

// Load configuration
PANO.sounds.forEach(function(s) {
	if (s.length < 2)
		return console.error("Invalid config", s);
	soundFiles.push(s[0]);
	soundPos.push(s[1]);
});
if (PANO.helper) {
	document.write('

');
}

var loadingDone = soundFiles.length;

initAura();
initScene();
initOrbs();
initUI();

animate(); // go!

function initAura() {
	var a = {};
	audio = a;

	// Detect if the audio context is supported.
	window.AudioContext = (
	  window.AudioContext ||
	  window.webkitAudioContext ||
	  null
	);
	if (!AudioContext) {
	  throw new Error("AudioContext not supported!");
	}

	a.context = new AudioContext();
	a.convolver = a.context.createConvolver();
	a.volume = a.context.createGain();

	a.mixer = a.context.createGain();

	a.flatGain = a.context.createGain();
	a.convolverGain = a.context.createGain();

	a.destination = a.mixer;
	a.mixer.connect(a.flatGain);
	//a.mixer.connect(a.convolver);
	a.convolver.connect(a.convolverGain);
	a.flatGain.connect(a.volume);
	a.convolverGain.connect(a.volume);
	a.volume.connect(a.context.destination);
}

function loadBuffer(soundFileName, callback) {
	var request = new XMLHttpRequest();
	request.open("GET", soundFileName, true);
	request.responseType = "arraybuffer";
	var ctx = audio.context;
	request.onload = function() {
	  ctx.decodeAudioData(request.response, function onSuccess(decodedBuffer) {
		callback(decodedBuffer);
	  }, function onFailure() {
		alert("Decoding the audio buffer failed");
	  });
	};
	request.send();
	return request;
}

function loadSound(soundFileName) {
	var ctx = audio.context;

	var sound = {};
	sound.source = ctx.createBufferSource();
	sound.source.loop = true;
	sound.panner = ctx.createPanner();
	sound.volume = ctx.createGain();

	sound.source.connect(sound.volume);
	sound.volume.connect(sound.panner);
	sound.panner.connect(audio.destination);

	setTimeout(function(){
		loadBuffer(soundFileName,
			function(buffer){
			  sound.buffer = buffer;
			  sound.source.buffer = sound.buffer;
			  loadProgress(1);
			});
	}, 250);

	return sound;
}

function loadProgress(dir) {
	loading += dir;
	var pc = parseInt(100 * (loading+1) / loadingDone) + '%';
	document.getElementById('pc').style.width = pc;
	document.getElementById('pctx').innerHTML = 'loading ' + pc;
}

function setPositionAndVelocity(object, audioNode, x, y, z, dt) {

	//var p = object.matrixWorld.getPosition();
	var p = new THREE.Vector3()
			.getPositionFromMatrix(object.matrixWorld);
	var px = p.x, py = p.y, pz = p.z;
	object.position.set(x,y,z);
	object.updateMatrixWorld();
	//var q = object.matrixWorld.getPosition();
	var q = new THREE.Vector3()
			.getPositionFromMatrix(object.matrixWorld);
	var dx = q.x-px, dy = q.y-py, dz = q.z-pz;
	audioNode.setPosition(q.x, q.y, q.z);
}

function setPosition(object, x, y, z, dt) {

	setPositionAndVelocity(object, object.sound.panner, x, y, z, dt);
	var vec = new THREE.Vector3(0,0,1);
	var m = object.matrixWorld;
	var mx = m.n14, my = m.n24, mz = m.n34;
	m.n14 = m.n24 = m.n34 = 0;
	vec.applyMatrix3(m);
	//m.multiplyVector3(vec);
	vec.normalize();
	object.sound.panner.setOrientation(vec.x, vec.y, vec.z);
	m.n14 = mx;
	m.n24 = my;
	m.n34 = mz;
}

function setListenerPosition(object, x, y, z, dt) {

	setPositionAndVelocity(object, audio.context.listener, x, y, z, dt);

	var m = object.matrix;
	var mx = m.n14, my = m.n24, mz = m.n34;
	m.n14 = m.n24 = m.n34 = 0;

	var vec = new THREE.Vector3(0,0,1);
	vec.applyMatrix3(m);
	//m.multiplyVector3(vec);
	vec.normalize();

	var up = new THREE.Vector3(0,-1,0);
	up.applyMatrix3(m);
	//m.multiplyVector3(up);
	up.normalize();

	audio.context.listener.setOrientation(vec.x, vec.y, vec.z, up.x, up.y, up.z);

	m.n14 = mx;
	m.n24 = my;
	m.n34 = mz;
}

function initScene() {

	var container, mesh;

	container = document.getElementById( 'container' );

	camera = new THREE.PerspectiveCamera( fov, window.innerWidth / window.innerHeight, 1, 1100 );
	camera.target = new THREE.Vector3( 0, 0, 0 );

	scene = new THREE.Scene();

	var geometry = new THREE.SphereGeometry( 500, 60, 40 );
	geometry.applyMatrix( new THREE.Matrix4().makeScale( -1, 1, 1 ) );

	var material = new THREE.MeshBasicMaterial( {
		map: THREE.ImageUtils.loadTexture( PANO.panorama )
	} );

	mesh = new THREE.Mesh( geometry, material );
	scene.add( mesh );

	renderer = new THREE.WebGLRenderer();
	renderer.setSize( window.innerWidth, window.innerHeight );
	container.appendChild( renderer.domElement );

	document.addEventListener( 'mousedown', onDocumentMouseDown, false );
	document.addEventListener( 'mousemove', onDocumentMouseMove, false );
	document.addEventListener( 'mouseup', onDocumentMouseUp, false );
	document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
	document.addEventListener( 'DOMMouseScroll', onDocumentMouseWheel, false);

	window.addEventListener( 'resize', onWindowResize, false );
} // -initScene

function onWindowResize() {
	camera.aspect = window.innerWidth / window.innerHeight;
	camera.updateProjectionMatrix();

	renderer.setSize( window.innerWidth, window.innerHeight );
}

function onDocumentMouseDown( event ) {
	event.preventDefault();

	isUserInteracting = true;

	onPointerDownPointerX = event.clientX;
	onPointerDownPointerY = event.clientY;

	onPointerDownLon = lon;
	onPointerDownLat = lat;
}

function intersectClick() {
	var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
	projector.unprojectVector( vector, camera );

	var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );

	var intersects = raycaster.intersectObjects( scene.children );

	if ( intersects.length > 0 ) {

		new TWEEN.Tween( intersects[ 0 ].object.position ).to( {
			x: Math.random() * 800 - 400,
			y: Math.random() * 800 - 400,
			z: Math.random() * 800 - 400 }, 2000 )
		.easing( TWEEN.Easing.Elastic.Out).start();

		new TWEEN.Tween( intersects[ 0 ].object.rotation ).to( {
			x: Math.random() * 2 * Math.PI,
			y: Math.random() * 2 * Math.PI,
			z: Math.random() * 2 * Math.PI }, 2000 )
		.easing( TWEEN.Easing.Elastic.Out).start();

	}
}

function onDocumentMouseMove( event ) {
	if ( isUserInteracting ) {
		lon = ( onPointerDownPointerX - event.clientX ) * 0.1 + onPointerDownLon;
		lat = ( event.clientY - onPointerDownPointerY ) * 0.1 + onPointerDownLat;
	}
}

function onDocumentMouseUp( event ) {
	isUserInteracting = false;
}

function onDocumentMouseWheel( event ) {
	// WebKit
	if ( event.wheelDeltaY ) {
		fov -= event.wheelDeltaY * 0.05;

	// Opera / Explorer 9
	} else if ( event.wheelDelta ) {
		fov -= event.wheelDelta * 0.05;

	// Firefox
	} else if ( event.detail ) {
		fov += event.detail * 1.0;
	}

	// Set bounds for zooming
	fov = Math.min(Math.max(fov, 20), 70);

	camera.projectionMatrix.makePerspective( fov, window.innerWidth / window.innerHeight, 1, 1100 );
	render();
}

function animate() {

	requestAnimationFrame(animate);

	if (loading < loadingDone) { return; }
	else if (loading == loadingDone) { // run once
		document.getElementById('loading').style.display = 'none';
		document.getElementById('controls').classList.remove('hide');
		var ctx = audio.context;
		orbs.forEach(function(c) {
			c.sound.source.start(ctx.currentTime + 0.020);
		});
		loading++;
	}

	TWEEN.update();
	updateAura();
	render();
}

function render() {

	lat = Math.max( - 85, Math.min( 85, lat ) );
	phi = THREE.Math.degToRad( 90 - lat );
	theta = THREE.Math.degToRad( lon );

	camera.target.x = 500 * Math.sin( phi ) * Math.cos( theta );
	camera.target.y = 500 * Math.cos( phi );
	camera.target.z = 500 * Math.sin( phi ) * Math.sin( theta );

	camera.lookAt( camera.target );

	/*
	// distortion
	camera.position.x = - camera.target.x;
	camera.position.y = - camera.target.y;
	camera.position.z = - camera.target.z;
	*/

	renderer.render( scene, camera );

}

function initOrbs() {

	var signs = [];

	var resolution = soundFiles.length;
	var amplitude = 20;
	var size = 360 / resolution;

	for (var i = 0; i < resolution; i++) {

		var segment = ( i*size ) * Math.PI/180;
		if (soundPos.length > i) {
			segment = soundPos[i] * Math.PI/180;
		}
		//console.log(i, segment * 180 / Math.PI);

		var X = Math.cos( segment ) * amplitude;
		var Z = Math.sin( segment ) * amplitude;

		var Y = 0;
		if (PANO.sounds[i].length > 2) {
			Y = PANO.sounds[i][2];
		}

		var cube = new THREE.Mesh(
				new THREE.CubeGeometry( 1, 1, 1 ),
				new THREE.MeshBasicMaterial( { color: 0xff0000 } )
			);
		cube.visible = false;
		cube.sound = loadSound('sound/' + soundFiles[i]);
		cube.soundFile = soundFiles[i].replace('.mp3', '');
		scene.add( cube );
		orbs.push(cube);

		var sign = new THREE.Mesh(
				new THREE.SphereGeometry( 5, 10, 10 ),
				new THREE.MeshBasicMaterial( { color: 0x0000ff, transparent: true, opacity: 0.4 } )
			);

		sign.visible = PANO.helper;
		sign.position.set(X, Y, Z);
		cube.origin = sign.position;
		cube.orient = segment;
		cube.orientdeg = segment * 180/Math.PI;

		scene.add( sign );
		signs.push(sign);
	}

} // -initOrbs

function updateAura() {

	var cp = camera.position;
	var camZ = cp.z, camX = cp.x, camY = cp.y;
	setListenerPosition(camera, camX, camY, camZ, 0);

	var cx = camX, cy = camY, cz = camZ;

	var cl = orbs.length;

	var degabs = (theta * 180/Math.PI) % 360;
	degabs = (degabs < 0) ? 360 + degabs : degabs;

	var maxdist = 0.5 * 360/cl;

	// Oscillating function
	orbs.forEach(function(c, i) {
		var dist = Math.abs(c.orientdeg - degabs);
		if (i == 0 && degabs > 180)
			dist = Math.abs(360 - degabs);

		if (dist > maxdist) {
			setPosition(c, 99999, 0, 0, 0);
		} else {
			var offset = Math.pow(( theta - c.orient )*5,3);
			setPosition(c, cx, cy, cz + offset, 0);
			currentOrb = i;

			// For debugging
			if (PANO.helper) {
				document.title = c.soundFile + " ~" + parseInt(degabs);
			}
		}
	});

} // -updateAura

function swingCam(a, b) {

	//lon = soundPos[b]; // testing
	var from = soundPos[a],
		to = soundPos[b];

	// Edges
	if (a == 0 && b > 1) {
		from = 360;
	} else if (b == 0 && a > 1) {
		to = 360;
	}

	// Start animation
	tween = new TWEEN
		.Tween({ lon: from })
		.to({ lon: to }, 2000 )
		.easing( TWEEN.Easing.Cubic.Out )
		.onUpdate(function () {
			lon = this.lon;
		}).start();

} // -swingTo

function initUI() {

	document.getElementById('next').onclick = function() {
		var from = currentOrb;
		currentOrb = (++currentOrb == orbs.length) ? 0 : currentOrb;
		swingCam(from, currentOrb);
	};

	document.getElementById('prev').onclick = function() {
		var from = currentOrb;
		currentOrb = (--currentOrb == -1) ? orbs.length-1 : currentOrb;
		swingCam(from, currentOrb);
	};

} // -initUI

}; // PANO.main<|endoftext|>


<commit_before>var fs = require('fs');
var libxmljs = require('libxmljs');

/**
 * Parse XPath template
 *
 * @param  {String}   templateFile Template filename
 * @param  {String}   xml          XML Data
 * @param  {String}   xpath        XPath
 * @param  {Function} callback
 */
exports.parse = function parse(tpl, xml, xpath, callback) {

	var xmlDoc
	try {
		xmlDoc = libxmljs.parseXmlString(xml);
	} catch (e) {
		return callback(e);
	}

	xmlDoc.root().find(xpath).forEach(function (el, index, arr) {

		var new_tpl = tpl.toString().replace(/\{\{(.*?)\}\}/g, function (match, token) {

			var splited = token.trim().split('/');
			var retVal = el.get(splited[0]);

			if(splited.length < 2) {
				retVal = retVal.text();
			}
			else {
				splited[1] = splited[1].substr(1);
				retVal = retVal.attr(splited[1]).value();
			}

	        return retVal;
	    });

	    callback(null, new_tpl);
	});
}

/**
 * Register engine for ExpressJS
 *
 * @param  {String}   filename Template filename
 * @param  {Object}   options  Options: XML & XPath
 * @param  {Function} callback
 */
exports.__express = function(filename, options, callback) {
	if (!options.xml)
		return callback({message: 'Error: Missing XML string'});
	if (!options.xpath)
		return callback({message: 'Error: Missing XPath string'});

	fs.readFile(filename, function(err, tpl) {
		if(err)
			return callback(err);

		parse(tpl, options.xml, options.xpath, function (err, data) {
			if(err)
				return callback(err);

			callback(null, data);
		});
	});
};<commit_msg>JSHint pass<commit_after>var fs = require('fs');
var libxmljs = require('libxmljs');

/**
 * Parse XPath template
 *
 * @param  {String}   templateFile Template filename
 * @param  {String}   xml          XML Data
 * @param  {String}   xpath        XPath
 * @param  {Function} callback
 */
exports.parse = function parse(tpl, xml, xpath, callback) {

	var xmlDoc;
	try {
		xmlDoc = libxmljs.parseXmlString(xml);
	} catch (e) {
		return callback(e);
	}

	xmlDoc.root().find(xpath).forEach(function (el, index, arr) {

		// Eval source: http://jsfiddle.net/hU7e2/1/
		var new_tpl = tpl.toString().replace(/\{\{(.*?)\}\}/g, function (match, token) {

			var splited = token.trim().split('/');
			var retVal = el.get(splited[0]);

			if(splited.length < 2) {
				retVal = retVal.text();
			}
			else {
				splited[1] = splited[1].substr(1);
				retVal = retVal.attr(splited[1]).value();
			}

	        return retVal;
	    });

	    callback(null, new_tpl);
	});
};

/**
 * Register engine for ExpressJS
 *
 * @param  {String}   filename Template filename
 * @param  {Object}   options  Options: XML & XPath
 * @param  {Function} callback
 */
exports.__express = function(filename, options, callback) {
	if (!options.xml)
		return callback({message: 'Error: Missing XML string'});
	if (!options.xpath)
		return callback({message: 'Error: Missing XPath string'});

	fs.readFile(filename, function(err, tpl) {
		if(err)
			return callback(err);

		parse(tpl, options.xml, options.xpath, function (err, data) {
			if(err)
				return callback(err);

			callback(null, data);
		});
	});
};<|endoftext|>


<commit_before>/*

peer.js - Peer representation within Gossipmonger.

The MIT License (MIT)

Copyright (c) 2013 Tristan Slominski

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

*/
"use strict";

/*
  * `options`: _Object_
    * `data`: _Object_ _(Default: `{}`)_ Peer data.
    * `id`: _String_ Id of the peer.
    * `intervals`: _Array_ _(Default: [750])_ An array of the last
            (up to `MAX_INTERVALS`) intervals between times when the peer has
            been seen.
    * `intervalsMean`: _Integer_ _(Default: undefined)_ Memoized intervals mean.
    * `lastTime`: _Integer_ _(Default: undefined)_ The last time the peer has been seen (in milliseconds
            since midnight Jan 1, 1970).
    * `live`: _Boolean_ _(Default: true)_ Indicator whether or not the peer is
            thought to be live.
    * `maxVersionSeen`: _Integer_ _(Default: 0)_ Vector clock value indicating
            the last version of the peer that has been observed.
    * `MAX_INTERVALS`: _Integer_ _(Default: 100)_ The maximum number of
            intervals to keep in `intervals`.
    * `sum`: _Integer_ _(Default: undefined)_ Memoized sum of intervals to make
            intervals mean calculation more efficient.
    * `transport`: _Any_ Any data identifying this peer to the transport mechanism
            that is required for correct transport operation.
*/
var Peer = module.exports = function Peer (options) {
    var self = this;

    options = options || {};

    self.data = options.data || {};
    self.id = options.id;
    self.intervals = options.intervals || [750];
    self.intervalsMean = options.intervalsMean;
    self.lastTime = options.lastTime;
    self.live = options.live === undefined ? true : options.live;
    self.maxVersionSeen = options.maxVersionSeen || 0;
    self.MAX_INTERVALS = options.MAX_INTERVALS || 100;
    self.sum = options.sum;
    self.transport = options.transport;

    if (self.sum === undefined)
        self.sum = Peer.calculateIntervalsSum(self.intervals);

    if (self.intervalsMean === undefined) {
        var length = self.intervals.length;
        if (!length)
            length = 1;

        self.intervalsMean = self.sum / length;
    }
};

/*
  * `intervals`: _Array_ Intervals to calculate the sum of.
  Return: _Integer_ Calculated sum.
*/
Peer.calculateIntervalsSum = function calculateIntervalsSum (intervals) {
    var self = this;

    var sum = 0;
    for (var i = 0; i < intervals.length; i++) {
        sum += intervals[i];
    }

    return sum;
};

/*
  * `sum`: _Integer_ Current sum.
  * `newInterval`: _Integer_ New interval being added to calculate the mean.
  * `oldInterval`: _Integer_ _(Default: undefined)_ Old interval being removed
          from being used in calculating the mean.
  Return: _Integer_ Calculated sum.
*/
Peer.calculateIntervalsSumEfficiently = function calculateIntervalsSumEfficiently (sum, newInterval, oldInterval) {
    var self = this;

    sum += newInterval;
    if (oldInterval)
        sum -= oldInterval;

    return sum;
};

/*
  * `version`: _Integer_ Vector clock version to construct deltas for.
  Return: _Array_ An array of deltas (ex delta: [key, value, version]).
*/
Peer.prototype.deltasAfterVersion = function deltasAfterVersion (version) {
    var self = this;

    var deltas = [];
    Object.keys(self.data).forEach(function (key) {
        var v = self.data[key][1]; // stored version
        if (v > version)
            deltas.push([key, self.data[key][0], v]);
    });

    return deltas;
};

/*
  * `time`: _Integer_ _(Default: `new Date().getTime()`)_ The time this peer has
          been seen (in milliseconds since midnight Jan 1, 1970).
*/
Peer.prototype.markContact = function markContact (time) {
    var self = this;

    if (!time)
        time = new Date().getTime();

    var interval;

    if (self.lastTime) {
        interval = time - self.lastTime;
    } else {
        interval = 750; // default interval
    }

    self.lastTime = time;
    self.intervals.push(interval);

    var oldInterval;
    if (self.intervals.length > self.MAX_INTERVALS)
        oldInterval = self.intervals.shift();

    self.sum =
        Peer.calculateIntervalsSumEfficiently(self.sum, interval, oldInterval);

    self.intervalsMean = self.sum / self.intervals.length;
};

Peer.prototype.markDead = function markDead () {
    var self = this;

    self.live = false;
}

Peer.prototype.markLive = function markLive () {
    var self = this;

    self.live = true;
};

/*
  Return: _Number_ Calculated phi for this peer.
*/
Peer.prototype.phi = function phi () {
    var self = this;

    var now = new Date().getTime();

    if (self.lastTime === undefined)
        self.lastTime = now - 750; // default interval

    var currentInterval = now - self.lastTime;
    var exp = -1 * currentInterval / self.intervalsMean;
    var p = Math.pow(Math.E, exp);
    return -1 * (Math.log(p) / Math.log(10));
};

/*
  * `key`: _String_ Key to update.
  * `value`: _Any_ The value to update with.
  Return: _String_ If the key has been updated, the key is returned, otherwise
          a `null` is returned instead.
*/
Peer.prototype.updateLocal = function updateLocal (key, value) {
    var self = this;

    // only update if the actual stored value is different from given value
    var stored = self.data[key];
    if (!deepEqual(stored, value)) {
        self.maxVersionSeen += 1;
        self.data[key] = [value, self.maxVersionSeen];
        return key;
    }

    return null;
};

/*
  * `key`: _String_ The key for the value to update.
  * `value`: _Any_ The value to update with.
  * `version`: _Integer_ The vector clock version of this key value pair.
  Return: _String_ If the key has been updated, the key is returned, otherwise
          a `null` is returned instead.
*/
Peer.prototype.updateWithDelta = function updateWithDelta (key, value, version) {
    var self = this;

    if (version > self.maxVersionSeen) {
        self.maxVersionSeen = version;
        self.data[key] = [value, version];
        return key;
    }

    return null;
};<commit_msg>peer.js has been refactored into separate gossipmonger-peer module.<commit_after><|endoftext|>


<commit_before>import React from 'react'
import Typeahead from '../ui/typeahead'
import Config from '../utils/config'
import Logger from '../utils/logger'
import _ from 'lodash'

export default class Search extends React.Component {

  static propTypes = {
    endpoint:      React.PropTypes.string,
    resultField:   React.PropTypes.string,
    query:         React.PropTypes.string,
    itemComponent: React.PropTypes.element,
    placeholder:   React.PropTypes.string
  }

  static defaultProps = {
    placeholder: 'Search'
  }

  static contextTypes = {
    history: React.PropTypes.object
  }

  constructor(props) {
    super(props)

    this.endpoint = props.endpoint || Config.get('menu.search.p', '/search')
    this.resultField = props.resultField || Config.get('menu.search.resultField', 'results')
    this.query    = props.query || Config.get('menu.search.queryParam', 'q')
  }

  render() {
    return (

        <Typeahead
          ref="input"
          categories
          endpoint={this.endpoint}
          query={this.query}
          resultField={this.resultField}
          placeholder={this.props.placeholder}
          onChooseResult={this.chooseResult.bind(this)}
          itemComponent={this.props.itemComponent}
          clearOnBlur
        />

    )
  }

  clear() {
    this.refs.input.clear()
  }

  chooseResult(result, type) {
    Logger.log(type, result)
    const route = _.get(this.props.routes, type, null)
    if(route) {
      this.context.history.push(_.template(route, {interpolate: Config.get('menu.search.urlInterpolate', _.templateSettings.interpolate)})(result))
    }
    this.refs.input.clear()
  }
}
<commit_msg>Fixed a misnamed configuration path in Search component<commit_after>import React from 'react'
import Typeahead from '../ui/typeahead'
import Config from '../utils/config'
import Logger from '../utils/logger'
import _ from 'lodash'

export default class Search extends React.Component {

  static propTypes = {
    endpoint:      React.PropTypes.string,
    resultField:   React.PropTypes.string,
    query:         React.PropTypes.string,
    itemComponent: React.PropTypes.element,
    placeholder:   React.PropTypes.string
  }

  static defaultProps = {
    placeholder: 'Search'
  }

  static contextTypes = {
    history: React.PropTypes.object
  }

  constructor(props) {
    super(props)

    this.endpoint = props.endpoint || Config.get('menu.search.endpoint', '/search')
    this.resultField = props.resultField || Config.get('menu.search.resultField', 'results')
    this.query    = props.query || Config.get('menu.search.queryParam', 'q')
  }

  render() {
    return (

        <Typeahead
          ref="input"
          categories
          endpoint={this.endpoint}
          query={this.query}
          resultField={this.resultField}
          placeholder={this.props.placeholder}
          onChooseResult={this.chooseResult.bind(this)}
          itemComponent={this.props.itemComponent}
          clearOnBlur
        />

    )
  }

  clear() {
    this.refs.input.clear()
  }

  chooseResult(result, type) {
    Logger.log(type, result)
    const route = _.get(this.props.routes, type, null)
    if(route) {
      this.context.history.push(_.template(route, {interpolate: Config.get('menu.search.urlInterpolate', _.templateSettings.interpolate)})(result))
    }
    this.refs.input.clear()
  }
}
<|endoftext|>


<commit_before>'use strict';

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import baseStyles from './baseStyles';
import BurgerIcon from './BurgerIcon';
import CrossIcon from './CrossIcon';

export default (styles) => {
  class Menu extends Component {
    constructor (props) {
      super(props);
      this.state = {
        isOpen: props && typeof props.isOpen !== 'undefined' ? props.isOpen : false
      };
    }

    toggleMenu() {
      const newState = { isOpen: !this.state.isOpen };

      this.applyWrapperStyles();

      this.setState(newState, () => {
        this.props.onStateChange(newState);

        // Timeout ensures wrappers are cleared after animation finishes.
        this.timeoutId && clearTimeout(this.timeoutId);
        this.timeoutId = setTimeout(() => {
          this.timeoutId = null;
          if (!newState.isOpen) {
            this.applyWrapperStyles(false);
          }
        }, 500);
      });
    }

    // Applies component-specific styles to external wrapper elements.
    applyWrapperStyles(set = true) {
      if (this.props.bodyClassName) {
        const body = document.querySelector('body');
        body.classList[set ? 'add' : 'remove'](this.props.bodyClassName);
      }

      if (styles.pageWrap && this.props.pageWrapId) {
        this.handleExternalWrapper(this.props.pageWrapId, styles.pageWrap, set);
      }

      if (styles.outerContainer && this.props.outerContainerId) {
        this.handleExternalWrapper(this.props.outerContainerId, styles.outerContainer, set);
      }
    }

    // Sets or unsets styles on DOM elements outside the menu component.
    // This is necessary for correct page interaction with some of the menus.
    // Throws and returns if the required external elements don't exist,
    // which means any external page animations won't be applied.
    handleExternalWrapper(id, wrapperStyles, set) {
      const html = document.querySelector('html');
      const body = document.querySelector('body');
      const wrapper = document.getElementById(id);

      if (!wrapper) {
        console.error("Element with ID '" + id + "' not found");
        return;
      }

      const builtStyles = this.getStyle(wrapperStyles);

      for (const prop in builtStyles) {
        if (builtStyles.hasOwnProperty(prop)) {
          wrapper.style[prop] = set ? builtStyles[prop] : '';
        }
      }

      // Prevent any horizontal scroll.
      [html, body].forEach((element) => {
        element.style['overflow-x'] = set ? 'hidden' : '';
      });
    }

    // Builds styles incrementally for a given element.
    getStyles(el, index, inline) {
      const propName = 'bm' + el.replace(el.charAt(0), el.charAt(0).toUpperCase());

      // Set base styles.
      let output = baseStyles[el] ? this.getStyle(baseStyles[el]) : {};

      // Add animation-specific styles.
      if (styles[el]) {
        output = {
          ...output,
          ...this.getStyle(styles[el], index + 1)
        };
      }

      // Add custom styles.
      if (this.props.styles[propName]) {
        output = {
          ...output,
          ...this.props.styles[propName]
        };
      }

      // Add element inline styles.
      if (inline) {
        output = {
          ...output,
          ...inline
        };
      }

      return output;
    }

    getStyle(style, index) {
      let width = this.props.width;
      if (typeof width !== 'string') width = `${width}px`;

      return style(this.state.isOpen, width, this.props.right, index);
    }

    listenForClose(e) {
      e = e || window.event;

      if (this.state.isOpen && (e.key === 'Escape' || e.keyCode === 27)) {
        this.toggleMenu();
      }
    }

    componentWillMount() {
      if (!styles) {
        throw new Error('No styles supplied');
      }

      // Allow initial open state to be set by props.
      if (this.props.isOpen) {
        this.toggleMenu();
      }
    }

    componentDidMount() {
      window.onkeydown = this.listenForClose.bind(this);

      // Allow initial open state to be set by props for animations with wrapper elements.
      if (this.props.isOpen) {
        this.toggleMenu();
      }
    }

    componentWillUnmount() {
      window.onkeydown = null;

      this.applyWrapperStyles(false);
    }

    componentDidUpdate() {
      if (styles.svg) {
        const morphShape = ReactDOM.findDOMNode(this, 'bm-morph-shape');
        const path = styles.svg.lib(morphShape).select('path');

        if (this.state.isOpen) {
          // Animate SVG path.
          styles.svg.animate(path);
        } else {
          // Reset path (timeout ensures animation happens off screen).
          setTimeout(() => {
            path.attr('d', styles.svg.pathInitial);
          }, 300);
        }
      }
    }

    componentWillReceiveProps(nextProps) {
      if (typeof nextProps.isOpen !== 'undefined' && nextProps.isOpen !== this.state.isOpen) {
        this.toggleMenu();
      }
    }

    render() {
      return (

          {!this.props.noOverlay && (
            <div
              className={`bm-overlay ${this.props.overlayClassName}`}
              onClick={() => !this.props.disableOverlayClick && this.toggleMenu()}
              style={this.getStyles('overlay')}
            />
          )}
          <div
            id={this.props.id}
            className={`bm-menu-wrap ${this.props.className}`}
            style={this.getStyles('menuWrap')}
          >
            {styles.svg && (
              <div className={`bm-morph-shape ${this.props.morphShapeClassName}`} style={this.getStyles('morphShape')}>
                <svg width="100%" height="100%" viewBox="0 0 100 800" preserveAspectRatio="none">
                  <path d={styles.svg.pathInitial}/>
                </svg>

            )}
            <div className={`bm-menu ${this.props.menuClassName}`} style={this.getStyles('menu')} >
              <nav className={`bm-item-list ${this.props.itemListClassName}`} style={this.getStyles('itemList')}>
                {React.Children.map(this.props.children, (item, index) => {
                  if (item) {
                    const extraProps = {
                      key: index,
                      style: this.getStyles('item', index, item.props.style)
                    };
                    return React.cloneElement(item, extraProps);
                  }
                })}

            {this.props.customCrossIcon !== false && (

                <CrossIcon
                  onClick={() => this.toggleMenu()}
                  styles={this.props.styles}
                  customIcon={this.props.customCrossIcon}
                  className={this.props.crossButtonClassName}
                  crossClassName={this.props.crossClassName}
                />

            )}

          {this.props.customBurgerIcon !== false && (

              <BurgerIcon
                onClick={() => this.toggleMenu()}
                styles={this.props.styles}
                customIcon={this.props.customBurgerIcon}
                className={this.props.burgerButtonClassName}
                barClassName={this.props.burgerBarClassName}
              />

          )}

      );
    }
  }

  Menu.propTypes = {
    bodyClassName: PropTypes.string,
    burgerBarClassName: PropTypes.string,
    burgerButtonClassName: PropTypes.string,
    crossButtonClassName: PropTypes.string,
    crossClassName: PropTypes.string,
    customBurgerIcon: PropTypes.oneOfType([PropTypes.element, PropTypes.oneOf([false])]),
    customCrossIcon: PropTypes.oneOfType([PropTypes.element, PropTypes.oneOf([false])]),
    disableOverlayClick: PropTypes.bool,
    id: PropTypes.string,
    isOpen: PropTypes.bool,
    itemListClassName: PropTypes.string,
    menuClassName: PropTypes.string,
    morphShapeClassName: PropTypes.string,
    noOverlay: PropTypes.bool,
    onStateChange: PropTypes.func,
    outerContainerId: styles && styles.outerContainer ? PropTypes.string.isRequired : PropTypes.string,
    overlayClassName: PropTypes.string,
    pageWrapId: styles && styles.pageWrap ? PropTypes.string.isRequired : PropTypes.string,
    right: PropTypes.bool,
    styles: PropTypes.object,
    width: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  };

  Menu.defaultProps = {
    bodyClassName: '',
    burgerBarClassName: '',
    burgerButtonClassName: '',
    className: '',
    crossButtonClassName: '',
    crossClassName: '',
    id: '',
    itemListClassName: '',
    menuClassName: '',
    morphShapeClassName: '',
    noOverlay: false,
    onStateChange: () => {},
    outerContainerId: '',
    overlayClassName: '',
    pageWrapId: '',
    styles: {},
    width: 300
  };

  return Menu;
};
<commit_msg>Allows disableOverlayClick to be a function<commit_after>'use strict';

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import baseStyles from './baseStyles';
import BurgerIcon from './BurgerIcon';
import CrossIcon from './CrossIcon';

export default (styles) => {
  class Menu extends Component {
    constructor (props) {
      super(props);
      this.state = {
        isOpen: props && typeof props.isOpen !== 'undefined' ? props.isOpen : false
      };
    }

    toggleMenu() {
      const newState = { isOpen: !this.state.isOpen };

      this.applyWrapperStyles();

      this.setState(newState, () => {
        this.props.onStateChange(newState);

        // Timeout ensures wrappers are cleared after animation finishes.
        this.timeoutId && clearTimeout(this.timeoutId);
        this.timeoutId = setTimeout(() => {
          this.timeoutId = null;
          if (!newState.isOpen) {
            this.applyWrapperStyles(false);
          }
        }, 500);
      });
    }

    // Applies component-specific styles to external wrapper elements.
    applyWrapperStyles(set = true) {
      if (this.props.bodyClassName) {
        const body = document.querySelector('body');
        body.classList[set ? 'add' : 'remove'](this.props.bodyClassName);
      }

      if (styles.pageWrap && this.props.pageWrapId) {
        this.handleExternalWrapper(this.props.pageWrapId, styles.pageWrap, set);
      }

      if (styles.outerContainer && this.props.outerContainerId) {
        this.handleExternalWrapper(this.props.outerContainerId, styles.outerContainer, set);
      }
    }

    // Sets or unsets styles on DOM elements outside the menu component.
    // This is necessary for correct page interaction with some of the menus.
    // Throws and returns if the required external elements don't exist,
    // which means any external page animations won't be applied.
    handleExternalWrapper(id, wrapperStyles, set) {
      const html = document.querySelector('html');
      const body = document.querySelector('body');
      const wrapper = document.getElementById(id);

      if (!wrapper) {
        console.error("Element with ID '" + id + "' not found");
        return;
      }

      const builtStyles = this.getStyle(wrapperStyles);

      for (const prop in builtStyles) {
        if (builtStyles.hasOwnProperty(prop)) {
          wrapper.style[prop] = set ? builtStyles[prop] : '';
        }
      }

      // Prevent any horizontal scroll.
      [html, body].forEach((element) => {
        element.style['overflow-x'] = set ? 'hidden' : '';
      });
    }

    // Builds styles incrementally for a given element.
    getStyles(el, index, inline) {
      const propName = 'bm' + el.replace(el.charAt(0), el.charAt(0).toUpperCase());

      // Set base styles.
      let output = baseStyles[el] ? this.getStyle(baseStyles[el]) : {};

      // Add animation-specific styles.
      if (styles[el]) {
        output = {
          ...output,
          ...this.getStyle(styles[el], index + 1)
        };
      }

      // Add custom styles.
      if (this.props.styles[propName]) {
        output = {
          ...output,
          ...this.props.styles[propName]
        };
      }

      // Add element inline styles.
      if (inline) {
        output = {
          ...output,
          ...inline
        };
      }

      return output;
    }

    getStyle(style, index) {
      let width = this.props.width;
      if (typeof width !== 'string') width = `${width}px`;

      return style(this.state.isOpen, width, this.props.right, index);
    }

    listenForClose(e) {
      e = e || window.event;

      if (this.state.isOpen && (e.key === 'Escape' || e.keyCode === 27)) {
        this.toggleMenu();
      }
    }

    shouldDisableOverlayClick() {
      if (typeof this.props.disableOverlayClick === 'function') {
        return this.props.disableOverlayClick();
      } else {
        return this.props.disableOverlayClick;
      }
    }

    componentWillMount() {
      if (!styles) {
        throw new Error('No styles supplied');
      }

      // Allow initial open state to be set by props.
      if (this.props.isOpen) {
        this.toggleMenu();
      }
    }

    componentDidMount() {
      window.onkeydown = this.listenForClose.bind(this);

      // Allow initial open state to be set by props for animations with wrapper elements.
      if (this.props.isOpen) {
        this.toggleMenu();
      }
    }

    componentWillUnmount() {
      window.onkeydown = null;

      this.applyWrapperStyles(false);
    }

    componentDidUpdate() {
      if (styles.svg) {
        const morphShape = ReactDOM.findDOMNode(this, 'bm-morph-shape');
        const path = styles.svg.lib(morphShape).select('path');

        if (this.state.isOpen) {
          // Animate SVG path.
          styles.svg.animate(path);
        } else {
          // Reset path (timeout ensures animation happens off screen).
          setTimeout(() => {
            path.attr('d', styles.svg.pathInitial);
          }, 300);
        }
      }
    }

    componentWillReceiveProps(nextProps) {
      if (typeof nextProps.isOpen !== 'undefined' && nextProps.isOpen !== this.state.isOpen) {
        this.toggleMenu();
      }
    }

    render() {
      return (

          {!this.props.noOverlay && (
            <div
              className={`bm-overlay ${this.props.overlayClassName}`}
              onClick={() => !this.shouldDisableOverlayClick() && this.toggleMenu()}
              style={this.getStyles('overlay')}
            />
          )}
          <div
            id={this.props.id}
            className={`bm-menu-wrap ${this.props.className}`}
            style={this.getStyles('menuWrap')}
          >
            {styles.svg && (
              <div className={`bm-morph-shape ${this.props.morphShapeClassName}`} style={this.getStyles('morphShape')}>
                <svg width="100%" height="100%" viewBox="0 0 100 800" preserveAspectRatio="none">
                  <path d={styles.svg.pathInitial}/>
                </svg>

            )}
            <div className={`bm-menu ${this.props.menuClassName}`} style={this.getStyles('menu')} >
              <nav className={`bm-item-list ${this.props.itemListClassName}`} style={this.getStyles('itemList')}>
                {React.Children.map(this.props.children, (item, index) => {
                  if (item) {
                    const extraProps = {
                      key: index,
                      style: this.getStyles('item', index, item.props.style)
                    };
                    return React.cloneElement(item, extraProps);
                  }
                })}

            {this.props.customCrossIcon !== false && (

                <CrossIcon
                  onClick={() => this.toggleMenu()}
                  styles={this.props.styles}
                  customIcon={this.props.customCrossIcon}
                  className={this.props.crossButtonClassName}
                  crossClassName={this.props.crossClassName}
                />

            )}

          {this.props.customBurgerIcon !== false && (

              <BurgerIcon
                onClick={() => this.toggleMenu()}
                styles={this.props.styles}
                customIcon={this.props.customBurgerIcon}
                className={this.props.burgerButtonClassName}
                barClassName={this.props.burgerBarClassName}
              />

          )}

      );
    }
  }

  Menu.propTypes = {
    bodyClassName: PropTypes.string,
    burgerBarClassName: PropTypes.string,
    burgerButtonClassName: PropTypes.string,
    crossButtonClassName: PropTypes.string,
    crossClassName: PropTypes.string,
    customBurgerIcon: PropTypes.oneOfType([PropTypes.element, PropTypes.oneOf([false])]),
    customCrossIcon: PropTypes.oneOfType([PropTypes.element, PropTypes.oneOf([false])]),
    disableOverlayClick: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
    id: PropTypes.string,
    isOpen: PropTypes.bool,
    itemListClassName: PropTypes.string,
    menuClassName: PropTypes.string,
    morphShapeClassName: PropTypes.string,
    noOverlay: PropTypes.bool,
    onStateChange: PropTypes.func,
    outerContainerId: styles && styles.outerContainer ? PropTypes.string.isRequired : PropTypes.string,
    overlayClassName: PropTypes.string,
    pageWrapId: styles && styles.pageWrap ? PropTypes.string.isRequired : PropTypes.string,
    right: PropTypes.bool,
    styles: PropTypes.object,
    width: PropTypes.oneOfType([PropTypes.number, PropTypes.string])
  };

  Menu.defaultProps = {
    bodyClassName: '',
    burgerBarClassName: '',
    burgerButtonClassName: '',
    className: '',
    crossButtonClassName: '',
    crossClassName: '',
    id: '',
    itemListClassName: '',
    menuClassName: '',
    morphShapeClassName: '',
    noOverlay: false,
    onStateChange: () => {},
    outerContainerId: '',
    overlayClassName: '',
    pageWrapId: '',
    styles: {},
    width: 300
  };

  return Menu;
};
<|endoftext|>


<commit_before>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2013 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License along with this program.  If not, see http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

describe("Create Facility Approved Product Controller", function () {

  beforeEach(module('openlmis'));

  var scope, parentScope, ctrl, supplyLine, $httpBackend, location, programs, facilityTypeList, messageService;

  beforeEach(inject(function ($rootScope, _$httpBackend_, $controller, $location, _messageService_) {
    scope = $rootScope.$new();
    parentScope = $rootScope.$new();
    scope.$parent = parentScope;
    $httpBackend = _$httpBackend_;
    location = $location;
    messageService = _messageService_;
    scope.query = "P10";
    supplyLine = {"program": {"name": "TB"}, "supplyingFacility": {"name": "supplying"}};

    programs = [
      {"name": "TB", "id": 1},
      {"name": "MALARIA", "id": 2}
    ];

    facilityTypeList = [
      {"id": 1, "name": "district1"},
      {"id": 2, "name": "district2"}
    ];
    ctrl = $controller('CreateFacilityApprovedProductController', {$scope: scope, facilityTypes: facilityTypeList, programs: programs, messageService: messageService});
  }));

  it("should load facility approved product", function () {
    parentScope.program = {"id": 2};
    parentScope.facilityType = {"id": 1};
    parentScope.facilityApprovedProductsModal = false;
    var product1 = {name: "n", code: "2 "};
    var product2 = {name: "a", code: "5 "};
    var product3 = {name: "n", code: "1 "};
    var productCategory1 = {name: "second product category", id: 1};
    var productCategory2 = {name: "first product category", id: 2};
    var programProductList = [
      {"product": product1, "productCategory": productCategory1},
      {"product": product2, "productCategory": productCategory2},
      {"product": product3, "productCategory": productCategory1}
    ];
    var data = {"programProductList": programProductList};

    $httpBackend.when("GET", '/programProducts/filter/programId/2/facilityTypeId/1.json').respond(data);
    scope.$parent.$apply(function () {
      scope.$parent.facilityApprovedProductsModal = true;
    });

    $httpBackend.flush();
    expect(scope.programProductList).toEqual(programProductList);
    expect(scope.productCategories).toEqual([productCategory1, productCategory2]);
  });

  it("should not load facility approved product if modal window is closed", function () {
    var httpBackendSpy = spyOn($httpBackend, 'expectGET');
    parentScope.facilityApprovedProductsModal = true;

    scope.$parent.$apply(function () {
      scope.$parent.facilityApprovedProductsModal = false;
    });

    expect(httpBackendSpy).not.toHaveBeenCalled();
  });

  it("should return headers", function () {
    spyOn(messageService, 'get');

    var header = scope.getHeader();
    expect(messageService.get).toHaveBeenCalledWith('header.code');
    expect(messageService.get).toHaveBeenCalledWith('header.name');
    expect(messageService.get).toHaveBeenCalledWith('header.strength');
    expect(messageService.get).toHaveBeenCalledWith('header.unit.of.measure');
    expect(messageService.get).toHaveBeenCalledWith('header.template.type');
    expect(header).toEqual("");
  });

  describe("Format Result and Selection", function () {
    it("should format results for full supply product", function () {
      spyOn(messageService, 'get').andReturn('Full supply');

      var product = {text: "productCode | productName | productStrength | productUnitOfMeasure | true"};
      var result = scope.formatResult(product);
      expect(result).toEqual("

productCode

 productName

 productStrength

 productUnitOfMeasure

Full supply

");
    });

    it("should format results for non full supply product", function () {
      spyOn(messageService, 'get').andReturn('Non full supply');

      var product = {text: "productCode | productName | productStrength | productUnitOfMeasure | false"};
      var result = scope.formatResult(product);
      expect(result).toEqual("

productCode

 productName

 productStrength

 productUnitOfMeasure

Non full supply

");
    });

    it("should format results for header", function () {
      var product = {text: "productCode | productName | productStrength | productUnitOfMeasure | type"};
      var result = scope.formatResult(product);
      expect(result).toEqual("

productCode

 productName

 productStrength

 productUnitOfMeasure

type

");
    });

    it("should return false for result if product is undefined", function () {
      var product = undefined;
      var result = scope.formatResult(product);
      expect(result).toBeFalsy();
    });

    it("should format selected value", function () {
      var product = {text: "productCode|productName|productStrength|productUnitOfMeasure|productType"};
      var result = scope.formatSelection(product);
      expect(result).toEqual("productCode - productName");
    });

    it("should return false for selection if product is undefined", function () {
      var product = undefined;
      var result = scope.formatSelection(product);
      expect(result).toBeFalsy();
    });
  });

  it("should filter products by category", function () {
    var product1 = {name: "n", code: "2 "};
    var product2 = {name: "a", code: "5 "};
    var product3 = {name: "n", code: "1 "};
    var productCategory1 = {name: "second product category", id: 1};
    var productCategory2 = {name: "first product category", id: 2};
    scope.programProductList = [
      {"product": product1, "productCategory": productCategory1},
      {"product": product2, "productCategory": productCategory2},
      {"product": product3, "productCategory": productCategory1}
    ];
    scope.productCategorySelected = productCategory1;

    scope.filterProductsByCategory();

    expect(scope.products).toEqual([product1, product3]);
  });

  describe("Add facility approved products button", function () {

    it("should enable when required fields are present", function () {
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: 12.34};
      scope.productCategorySelected = {};
      scope.productSelected = {};

      expect(scope.isAddDisabled()).toBeFalsy();
    });

    it("should disable when maxMonthStock not present", function () {
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: undefined};
      scope.productCategorySelected = {};
      scope.productSelected = {};

      expect(scope.isAddDisabled()).toBeTruthy();
    });

    it("should disable when product category is not selected", function () {
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: 12.34};
      scope.productCategorySelected = undefined;
      scope.productSelected = {};

      expect(scope.isAddDisabled()).toBeTruthy();
    });

    it("should disable when product not selected", function () {
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: 12.34};
      scope.productCategorySelected = {};
      scope.productSelected = undefined;

      expect(scope.isAddDisabled()).toBeTruthy();
    });

    it("should disable newFacilityTypeApprovedProduct is not present", function () {
      scope.newFacilityTypeApprovedProduct = undefined;
      scope.productCategorySelected = {};
      scope.productSelected = undefined;

      expect(scope.isAddDisabled()).toBeTruthy();
    });
  });

  describe('Add facility type approved product', function () {
    var facilityType, program, productSelectedString, product1, product2, product3, productCategory1, productCategory2;

    beforeEach(function () {
      product1 = {name: "n", code: "2"};
      product2 = {name: "a", code: "5"};
      product3 = {name: "n", code: "1"};
      productCategory1 = {name: "second product category", id: 1};
      productCategory2 = {name: "first product category", id: 2};

      facilityType = {name: "facility type"};
      program = {name: "program"};
      productSelectedString = "{\"name\": \"n\", \"code\": \"2\"}";

      parentScope.facilityType = facilityType;
      parentScope.program = program;
      scope.productSelected = productSelectedString;
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: 1, minMonthsOfStock: 13, eop: 12};
      scope.programProductList = [
        {"product": product1, "productCategory": productCategory1},
        {"product": product2, "productCategory": productCategory2},
        {"product": product3, "productCategory": productCategory1}
      ];

    });

    it('should add a new filled product to the added products list and clear modal data', function () {

      scope.addFacilityTypeApprovedProduct();

      expect(scope.addedFacilityTypeApprovedProducts).toEqual([
        {
          facilityType: facilityType,
          programProduct: {program: program, product: product1},
          maxMonthsOfStock: 1,
          minMonthsOfStock: 13,
          eop: 12
        }
      ]);
      expect(scope.programProductList).toEqual([
        {"product": product2, "productCategory": productCategory2},
        {"product": product3, "productCategory": productCategory1}
      ]);
      expect(scope.productCategorySelected).toBeUndefined();
      expect(scope.productSelected).toBeUndefined();
      expect(scope.products).toBeUndefined();
      expect(scope.newFacilityTypeApprovedProduct).toBeUndefined();
    });
  });
});
<commit_msg>|#1490| +Riya| Removing unnecessary line from test.<commit_after>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2013 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
 * You should have received a copy of the GNU Affero General Public License along with this program.  If not, see http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

describe("Create Facility Approved Product Controller", function () {

  beforeEach(module('openlmis'));

  var scope, parentScope, ctrl, supplyLine, $httpBackend, location, programs, facilityTypeList, messageService;

  beforeEach(inject(function ($rootScope, _$httpBackend_, $controller, $location, _messageService_) {
    scope = $rootScope.$new();
    parentScope = $rootScope.$new();
    scope.$parent = parentScope;
    $httpBackend = _$httpBackend_;
    location = $location;
    messageService = _messageService_;
    scope.query = "P10";
    supplyLine = {"program": {"name": "TB"}, "supplyingFacility": {"name": "supplying"}};

    programs = [
      {"name": "TB", "id": 1},
      {"name": "MALARIA", "id": 2}
    ];

    facilityTypeList = [
      {"id": 1, "name": "district1"},
      {"id": 2, "name": "district2"}
    ];
    ctrl = $controller('CreateFacilityApprovedProductController', {$scope: scope, facilityTypes: facilityTypeList, programs: programs, messageService: messageService});
  }));

  it("should load facility approved product", function () {
    parentScope.program = {"id": 2};
    parentScope.facilityType = {"id": 1};
    parentScope.facilityApprovedProductsModal = false;
    var product1 = {name: "n", code: "2 "};
    var product2 = {name: "a", code: "5 "};
    var product3 = {name: "n", code: "1 "};
    var productCategory1 = {name: "second product category", id: 1};
    var productCategory2 = {name: "first product category", id: 2};
    var programProductList = [
      {"product": product1, "productCategory": productCategory1},
      {"product": product2, "productCategory": productCategory2},
      {"product": product3, "productCategory": productCategory1}
    ];
    var data = {"programProductList": programProductList};

    $httpBackend.when("GET", '/programProducts/filter/programId/2/facilityTypeId/1.json').respond(data);
    scope.$parent.$apply(function () {
      scope.$parent.facilityApprovedProductsModal = true;
    });

    $httpBackend.flush();
    expect(scope.programProductList).toEqual(programProductList);
    expect(scope.productCategories).toEqual([productCategory1, productCategory2]);
  });

  it("should not load facility approved product if modal window is closed", function () {
    var httpBackendSpy = spyOn($httpBackend, 'expectGET');
    parentScope.facilityApprovedProductsModal = true;

    scope.$parent.$apply(function () {
      scope.$parent.facilityApprovedProductsModal = false;
    });

    expect(httpBackendSpy).not.toHaveBeenCalled();
  });

  it("should return headers", function () {
    spyOn(messageService, 'get');

    var header = scope.getHeader();
    expect(messageService.get).toHaveBeenCalledWith('header.code');
    expect(messageService.get).toHaveBeenCalledWith('header.name');
    expect(messageService.get).toHaveBeenCalledWith('header.strength');
    expect(messageService.get).toHaveBeenCalledWith('header.unit.of.measure');
    expect(messageService.get).toHaveBeenCalledWith('header.template.type');
  });

  describe("Format Result and Selection", function () {
    it("should format results for full supply product", function () {
      spyOn(messageService, 'get').andReturn('Full supply');

      var product = {text: "productCode | productName | productStrength | productUnitOfMeasure | true"};
      var result = scope.formatResult(product);
      expect(result).toEqual("

productCode

 productName

 productStrength

 productUnitOfMeasure

Full supply

");
    });

    it("should format results for non full supply product", function () {
      spyOn(messageService, 'get').andReturn('Non full supply');

      var product = {text: "productCode | productName | productStrength | productUnitOfMeasure | false"};
      var result = scope.formatResult(product);
      expect(result).toEqual("

productCode

 productName

 productStrength

 productUnitOfMeasure

Non full supply

");
    });

    it("should format results for header", function () {
      var product = {text: "productCode | productName | productStrength | productUnitOfMeasure | type"};
      var result = scope.formatResult(product);
      expect(result).toEqual("

productCode

 productName

 productStrength

 productUnitOfMeasure

type

");
    });

    it("should return false for result if product is undefined", function () {
      var product = undefined;
      var result = scope.formatResult(product);
      expect(result).toBeFalsy();
    });

    it("should format selected value", function () {
      var product = {text: "productCode|productName|productStrength|productUnitOfMeasure|productType"};
      var result = scope.formatSelection(product);
      expect(result).toEqual("productCode - productName");
    });

    it("should return false for selection if product is undefined", function () {
      var product = undefined;
      var result = scope.formatSelection(product);
      expect(result).toBeFalsy();
    });
  });

  it("should filter products by category", function () {
    var product1 = {name: "n", code: "2 "};
    var product2 = {name: "a", code: "5 "};
    var product3 = {name: "n", code: "1 "};
    var productCategory1 = {name: "second product category", id: 1};
    var productCategory2 = {name: "first product category", id: 2};
    scope.programProductList = [
      {"product": product1, "productCategory": productCategory1},
      {"product": product2, "productCategory": productCategory2},
      {"product": product3, "productCategory": productCategory1}
    ];
    scope.productCategorySelected = productCategory1;

    scope.filterProductsByCategory();

    expect(scope.products).toEqual([product1, product3]);
  });

  describe("Add facility approved products button", function () {

    it("should enable when required fields are present", function () {
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: 12.34};
      scope.productCategorySelected = {};
      scope.productSelected = {};

      expect(scope.isAddDisabled()).toBeFalsy();
    });

    it("should disable when maxMonthStock not present", function () {
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: undefined};
      scope.productCategorySelected = {};
      scope.productSelected = {};

      expect(scope.isAddDisabled()).toBeTruthy();
    });

    it("should disable when product category is not selected", function () {
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: 12.34};
      scope.productCategorySelected = undefined;
      scope.productSelected = {};

      expect(scope.isAddDisabled()).toBeTruthy();
    });

    it("should disable when product not selected", function () {
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: 12.34};
      scope.productCategorySelected = {};
      scope.productSelected = undefined;

      expect(scope.isAddDisabled()).toBeTruthy();
    });

    it("should disable newFacilityTypeApprovedProduct is not present", function () {
      scope.newFacilityTypeApprovedProduct = undefined;
      scope.productCategorySelected = {};
      scope.productSelected = undefined;

      expect(scope.isAddDisabled()).toBeTruthy();
    });
  });

  describe('Add facility type approved product', function () {
    var facilityType, program, productSelectedString, product1, product2, product3, productCategory1, productCategory2;

    beforeEach(function () {
      product1 = {name: "n", code: "2"};
      product2 = {name: "a", code: "5"};
      product3 = {name: "n", code: "1"};
      productCategory1 = {name: "second product category", id: 1};
      productCategory2 = {name: "first product category", id: 2};

      facilityType = {name: "facility type"};
      program = {name: "program"};
      productSelectedString = "{\"name\": \"n\", \"code\": \"2\"}";

      parentScope.facilityType = facilityType;
      parentScope.program = program;
      scope.productSelected = productSelectedString;
      scope.newFacilityTypeApprovedProduct = {maxMonthsOfStock: 1, minMonthsOfStock: 13, eop: 12};
      scope.programProductList = [
        {"product": product1, "productCategory": productCategory1},
        {"product": product2, "productCategory": productCategory2},
        {"product": product3, "productCategory": productCategory1}
      ];

    });

    it('should add a new filled product to the added products list and clear modal data', function () {

      scope.addFacilityTypeApprovedProduct();

      expect(scope.addedFacilityTypeApprovedProducts).toEqual([
        {
          facilityType: facilityType,
          programProduct: {program: program, product: product1},
          maxMonthsOfStock: 1,
          minMonthsOfStock: 13,
          eop: 12
        }
      ]);
      expect(scope.programProductList).toEqual([
        {"product": product2, "productCategory": productCategory2},
        {"product": product3, "productCategory": productCategory1}
      ]);
      expect(scope.productCategorySelected).toBeUndefined();
      expect(scope.productSelected).toBeUndefined();
      expect(scope.products).toBeUndefined();
      expect(scope.newFacilityTypeApprovedProduct).toBeUndefined();
    });
  });
});
<|endoftext|>


<commit_before>var _ = require('lodash')
var EventEmitter = require('eventemitter2').EventEmitter2
var State = require('./state.js')
var formatEvent = require('./util/events.js').formatEvent

module.exports = function (id, level, opts) {
  opts = opts || {size: 700}

  function load (level) {
    var tmp
    var maps = []
    level.maps.forEach(function (map) {
      map.start.forEach(function (start) {
        tmp = _.cloneDeep(map)
        tmp.start = [start]
        maps.push(tmp)
      })
    })
    var config = _.cloneDeep(level.config)
    config.stages = maps.length
    return {maps: maps, config: config}
  }

  level = load(level)

  var container = document.getElementById(id)

  var main = require('./ui/main.js')(container, opts)
  var score = require('./ui/score.js')(container)
  var stages = require('./ui/stages.js')(container)
  var moves = require('./ui/moves.js')(container)
  var lives = require('./ui/lives.js')(container)
  var message = require('./ui/message.js')(container)

  var state = new State(level.config)

  var events = new EventEmitter()

  var game = require('./game.js')(main.canvas, level.maps[0])

  game.events.on(['player', 'collect'], function (event) {
    state.score.current += 10
    score.update(state.score)
  })

  game.events.on(['player', 'exit'], function (event) {
    state.moves.current -= 1
    moves.update(state.moves)
  })

  game.events.on(['player', 'enter'], function (event) {
    if (_.isEqual(event.tile, level.maps[state.stages.current].target)) {
      completed()
    } else {
      failed()
    }
  })

  game.events.onAny(function (event) {
    events.emit(this.event, event)
  })

  function completed () {
    events.emit(['map', 'completed'], formatEvent({ map: state.stages.current }))
    if (state.stages.current === state.stages.total - 1) {
      game.flash()
      events.emit(['level', 'completed'], formatEvent({ level: level.config.name }))
      setTimeout(function () {
        main.hide()
        message.show('LEVEL COMPLETE')
      }, 1000)
    } else {
      game.flash()
      state.stages.current += 1
      stages.update(state.stages)
      state.score.current += 1000
      state.moves.current = state.moves.total
      score.update(state.score)
      moves.update(state.moves)
      setTimeout(function () {
        main.hide()
        message.show('YOU DID IT!')
        setTimeout(function () {
          game.reload(level.maps[state.stages.current])
          message.hide()
          main.show()
        }, 1000)
      }, 600)
    }
  }

  function failed () {
    if (state.moves.current === 0 & state.lives.current === 1) {
      events.emit(['level', 'failed'], formatEvent({ level: level.config.name }))
      state.lives.current -= 1
      lives.update(state.lives)
      main.hide()
      message.show('GAME OVER')
    }

    if (state.moves.current === 0 & state.lives.current > 1) {
      main.hide()
      message.show('OUT OF STEPS TRY AGAIN')
      events.emit(['map', 'failed'], formatEvent({ map: state.stages.current }))
      state.lives.current -= 1
      lives.update(state.lives)
      setTimeout(function () {
        state.moves.current = state.moves.total
        moves.update(state.moves)
        game.moveto(level.maps[state.stages.current].start[0])
        message.hide()
        main.show()
      }, 1000)
    }
  }

  function start () {
    if (state.stages.current === 0 && state.lives.current === state.lives.total) {
      events.emit(['level', 'started'], formatEvent({ level: level.config.name }))
    }
    score.update(state.score)
    stages.update(state.stages)
    moves.update(state.moves)
    lives.update(state.lives)
    score.show()
    stages.show()
    moves.show()
    lives.show()
    main.hide()
    message.show('GET READY!')
    events.emit(['map', 'started'], formatEvent({ map: state.stages.current }))
    setTimeout(function () {
      message.hide()
      main.show()
    }, 1000)
    game.start()
  }

  return {
    pause: function () {
      game.pause()
    },

    resume: function () {
      game.resume()
    },

    reload: function (updated) {
      level = load(updated)
      state.reload(level.config)
      game.reload(level.maps[state.stages.current])
      start()
    },

    start: start,

    events: events
  }
}
<commit_msg>Update score based on steps remaining<commit_after>var _ = require('lodash')
var EventEmitter = require('eventemitter2').EventEmitter2
var State = require('./state.js')
var formatEvent = require('./util/events.js').formatEvent

module.exports = function (id, level, opts) {
  opts = opts || {size: 700}

  function load (level) {
    var tmp
    var maps = []
    level.maps.forEach(function (map) {
      map.start.forEach(function (start) {
        tmp = _.cloneDeep(map)
        tmp.start = [start]
        maps.push(tmp)
      })
    })
    var config = _.cloneDeep(level.config)
    config.stages = maps.length
    return {maps: maps, config: config}
  }

  level = load(level)

  var container = document.getElementById(id)

  var main = require('./ui/main.js')(container, opts)
  var score = require('./ui/score.js')(container)
  var stages = require('./ui/stages.js')(container)
  var moves = require('./ui/moves.js')(container)
  var lives = require('./ui/lives.js')(container)
  var message = require('./ui/message.js')(container)

  var state = new State(level.config)

  var events = new EventEmitter()

  var game = require('./game.js')(main.canvas, level.maps[0])

  game.events.on(['player', 'collect'], function (event) {
    state.score.current += 10
    score.update(state.score)
  })

  game.events.on(['player', 'exit'], function (event) {
    state.moves.current -= 1
    moves.update(state.moves)
  })

  game.events.on(['player', 'enter'], function (event) {
    if (_.isEqual(event.tile, level.maps[state.stages.current].target)) {
      completed()
    } else {
      failed()
    }
  })

  game.events.onAny(function (event) {
    events.emit(this.event, event)
  })

  function completed () {
    events.emit(['map', 'completed'], formatEvent({ map: state.stages.current }))
    state.score.current += 1000 + state.moves.current * 100
    score.update(state.score)
    if (state.stages.current === state.stages.total - 1) {
      game.flash()
      events.emit(['level', 'completed'], formatEvent({ level: level.config.name }))
      setTimeout(function () {
        main.hide()
        message.show('LEVEL COMPLETE')
      }, 1000)
    } else {
      game.flash()
      state.stages.current += 1
      stages.update(state.stages)
      state.moves.current = state.moves.total
      score.update(state.score)
      moves.update(state.moves)
      setTimeout(function () {
        main.hide()
        message.show('YOU DID IT!')
        setTimeout(function () {
          game.reload(level.maps[state.stages.current])
          message.hide()
          main.show()
        }, 1000)
      }, 600)
    }
  }

  function failed () {
    if (state.moves.current === 0 & state.lives.current === 1) {
      events.emit(['level', 'failed'], formatEvent({ level: level.config.name }))
      state.lives.current -= 1
      lives.update(state.lives)
      main.hide()
      message.show('GAME OVER')
    }

    if (state.moves.current === 0 & state.lives.current > 1) {
      main.hide()
      message.show('OUT OF STEPS TRY AGAIN')
      events.emit(['map', 'failed'], formatEvent({ map: state.stages.current }))
      state.lives.current -= 1
      lives.update(state.lives)
      setTimeout(function () {
        state.moves.current = state.moves.total
        moves.update(state.moves)
        game.moveto(level.maps[state.stages.current].start[0])
        message.hide()
        main.show()
      }, 1000)
    }
  }

  function start () {
    if (state.stages.current === 0 && state.lives.current === state.lives.total) {
      events.emit(['level', 'started'], formatEvent({ level: level.config.name }))
    }
    score.update(state.score)
    stages.update(state.stages)
    moves.update(state.moves)
    lives.update(state.lives)
    score.show()
    stages.show()
    moves.show()
    lives.show()
    main.hide()
    message.show('GET READY!')
    events.emit(['map', 'started'], formatEvent({ map: state.stages.current }))
    setTimeout(function () {
      message.hide()
      main.show()
    }, 1000)
    game.start()
  }

  return {
    pause: function () {
      game.pause()
    },

    resume: function () {
      game.resume()
    },

    reload: function (updated) {
      level = load(updated)
      state.reload(level.config)
      game.reload(level.maps[state.stages.current])
      start()
    },

    start: start,

    events: events
  }
}
<|endoftext|>