<commit_before>/*
 * Audiovisualization using the html canvas element.
 * Please note: this is an offline-only version. For this version to be used online you need an XMLHttpRequest to load the music file!
 * ©2017, Dominik Hofacker
 * https://www.behance.net/dominikhofacker
 * Please consider supporting this project on behance:
 * https://www.behance.net/gallery/49260123/Web-Audio-Visualization
 */

var rafID = null;
var analyser = null;
var c = null;
var cDraw = null;
var ctx = null;
var micropone = null;
var ctxDraw = null;

var loader;
var filename;
var renderer;
var skull;
var sunglasses;
var headphones;
var pivot;
var cube_mesh;
var scene;
var camera;
var controls;
var fileChosen = false;
var RADIUS = 2;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var loadedVar = 0, totalVar = 0;
var isTouchDevice = false;

const HIGHLIGHT_COLORS = [0x4200ff, 0x00ffff, 0xff0000, 0xff00ff];
const LOADING_WRAPPER_HEIGHT = 100;

//handle different prefix of the audio context
var AudioContext = AudioContext || webkitAudioContext;
//create the context.
var context = new AudioContext();

//using requestAnimationFrame instead of timeout...
if (!window.requestAnimationFrame)
	window.requestAnimationFrame = window.webkitRequestAnimationFrame;

$(function () {

		/*$(".inputfile + label, .button").addClass("animated fadeInUp");
		$('.inputfile + label, .button').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
                $('.inputfile + label, .button').removeClass('fadeInUp');
         });*/

		$('#loading_wrapper').css("top", ($(window).height() / 2 - LOADING_WRAPPER_HEIGHT));

		//handle different types navigator objects of different browsers
		navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
	            navigator.mozGetUserMedia || navigator.msGetUserMedia;
	    //eigene Init
	    loader = new BufferLoader();
            //loader.visualize = visualize;
            //init canvas
	    initBinCanvas();
		//start updating
		rafID = window.requestAnimationFrame(updateVisualization);

});

function handleFiles(files) {
    if(files.length === 0){
        return;
    }
	fileChosen = true;
    setupAudioNodes();

	var fileReader  = new FileReader();
    fileReader.onload = function(){
         var arrayBuffer = this.result;
         console.log(arrayBuffer);
         console.log(arrayBuffer.byteLength);
     };
     fileReader.readAsArrayBuffer(files[0]);
     var url = URL.createObjectURL(files[0]);

	var request = new XMLHttpRequest();
	request.open('GET', url, true);
	request.responseType = 'arraybuffer';

 	// When loaded decode the data
	request.onload = function() {
		// decode the data
		context.decodeAudioData(request.response, function(buffer) {
		// when the audio is decoded play the sound
		sourceNode.buffer = buffer;
		sourceNode.start(0);
		//on error
		}, function(e) {
			console.log(e);
		});
	};
	request.send();

	camera.position.z = 375;
	camera.position.y = 0;

	$(".inputfile + label, .button").addClass("animated fadeOutDown");
	$("#viewer_discretion").html("Copyright 2017, Dominik Hofacker. All Rights Reserved.");
}

function playSample() {

	fileChosen = true;
    setupAudioNodes();

	var request = new XMLHttpRequest();
	request.open('GET', 'src/sample.mp3', true);
	request.responseType = 'arraybuffer';

 	// When loaded decode the data
	request.onload = function() {
		// decode the data
		context.decodeAudioData(request.response, function(buffer) {
		// when the audio is decoded play the sound
		sourceNode.buffer = buffer;
		sourceNode.start(0);
		//on error
		}, function(e) {
			console.log(e);
		});
	};
	request.send();

	camera.position.z = 375;
	camera.position.y = 0;

	$(".inputfile + label, .button").addClass("animated fadeOutDown");
	$("#viewer_discretion").html("You are listening to Jordan Schor - Cosmic (feat. Nathan Brumley).");
}

function initBinCanvas () {

	//SCENE#######################################################################
	scene = new THREE.Scene();
	scene.background = new THREE.Color( 0x00000);
	camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000);
	renderer = new THREE.WebGLRenderer();
	renderer.setSize(window.innerWidth, window.innerHeight);
	renderer.setPixelRatio( window.devicePixelRatio );
	document.body.appendChild(renderer.domElement);

	controls = new THREE.OrbitControls( camera, renderer.domElement );

	controls.enabled = false;

	var geometry = new THREE.BoxGeometry(560, 560, 100000, 15, 55, 100);

	var cubeMat = new THREE.MeshBasicMaterial({color: '#4200ff', wireframe: true});
	var testMat = new THREE.MeshStandardMaterial({
		roughness: 0,
		color: 'white'
	});
	cube_mesh = new THREE.Mesh(geometry, cubeMat);
	scene.add(cube_mesh);

	//MATERIALS###################################################################
	var material = new THREE.MeshBasicMaterial({vertexColors: THREE.VertexColors});
	var objMat = new THREE.MeshStandardMaterial({
		color: 'yellow',
		wireframe: true,
		roughness: .3
	});
	var newMat = new THREE.MeshPhongMaterial({
		color: '#6e3bff'
	});
	var headphoneMat = new THREE.MeshStandardMaterial({
		roughness: 0.4,
		color: 'yellow',
		wireframe: false
	});
	var box = new THREE.Box3();
	pivot = new THREE.Group(); //group objs and center pivot of skull

	//LOADING OBJs#################################################################
	var manager = new THREE.LoadingManager();
		manager.onProgress = function ( item, loaded, total ) {
			console.log( item, loaded, total ); //loaded 1 total 3
			loadedVar = loaded;
			totalVar = total;
	};
	var onProgress = function ( xhr ) {
		if ( xhr.lengthComputable ) {
			var percentComplete = xhr.loaded / xhr.total * 100;
			console.log( Math.round(percentComplete, 2) + '% loaded' );
			$(".label").html(Math.round(percentComplete, 2) + '% loaded');
			//console.log("LOADED VAR:" + loadedVar + "\n TOTAL VAR:" + totalVar);
			if (percentComplete === 100 && loadedVar === 2) {
				$(".inputfile + label, .button").css("visibility", "visible");
				$(".inputfile + label, .button").addClass("animated fadeInUp");
				$('.inputfile + label, .button').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
						$('.inputfile + label, .button').removeClass('fadeInUp');
				});
				$("#loading_screen").addClass("animated fadeOut");
				$("#loading_screen").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
						$("#loading_screen").css("display", "none");
						$("#viewer_discretion").html("This music visualizer may be harmful to viewers who are prone to epilepsy. Viewer discretion is advised.");
				});
			}
		}
	};
	var onError = function ( xhr ) {
	};
	var loader3D = new THREE.OBJLoader( manager );
	// load a resource
	loader3D.load(
		// resource URL
		'src/skull_lowpoly.obj',
		// Function when resource is loaded
		function ( object ) {
			object.traverse(function (child) {
				if ( child instanceof THREE.Mesh ) {

						child.material = newMat;
						child.shading = THREE.FlatShading;

					}
			});
			object.position.y = 0;
			object.position.x = 0;
			box.setFromObject( object );
			box.getCenter( object.position ); // this re-sets the mesh position
			object.position.multiplyScalar( - 1 );
			pivot.add( object );
			skull = object;
	}, onProgress, onError );

	loader3D.load(
		'src/sunglasses.obj',
		function ( object ) {
			object.traverse(function( child ) {
				if (child instanceof THREE.Mesh) {
					child.material = objMat;
					child.shading = THREE.SmoothShading;
				}
			});
			object.position.x = 0;
			object.position.y = -3;
			object.position.z = 10;
			pivot.add( object );
			sunglasses = object;
	}, onProgress, onError );

	loader3D.load(
		'src/headphones.obj',
		function ( object ) {
			object.traverse(function( child ) {
				if (child instanceof THREE.Mesh) {
					child.material = headphoneMat;
					child.shading = THREE.SmoothShading;
				}
			});
			object.position.x = 0;
			object.position.y = 3;
			object.position.z = 30;
			pivot.add( object );
			headphones = object;
	}, onProgress, onError );

	scene.add( pivot );

	//scene.add(skull);
	//camera.position.z = 375;
	camera.position.z = 500;
	camera.position.y = 500;

	var ambient = new THREE.AmbientLight( 0x101030 );
	scene.add( ambient );

	var directionalLight = new THREE.DirectionalLight( 0xffeedd );
	directionalLight.position.set( 0, 0, 1 );
	scene.add( directionalLight );

	document.addEventListener( 'mousemove', onDocumentMouseMove, false );
	window.addEventListener( 'resize', onWindowResize, false );
	window.addEventListener('touchstart', function() {
	  // the user touched the screen!
		isTouchDevice = true;
		controls.addEventListener( 'change', render ); // remove when using animation loop
		// enable animation loop when using damping or autorotation
		controls.enableDamping = true;
		controls.dampingFactor = 0.5;
		controls.enableZoom = false;
		controls.noPan = true;
		// How far you can orbit vertically, upper and lower limits.
		// Range is 0 to Math.PI radians.
		controls.minPolarAngle = 0.35 * Math.PI; // radians
		controls.maxPolarAngle = 0.65 * Math.PI; // radians

		// How far you can orbit horizontally, upper and lower limits.
		// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
		controls.minAzimuthAngle = - Math.PI / 6; // radians
		controls.maxAzimuthAngle = Math.PI / 6; // radians
		controls.enabled = true;
	});

}

function onWindowResize() {

	windowHalfX = window.innerWidth / 2;
	windowHalfY = window.innerHeight / 2;

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

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

	$('#loading_wrapper').css("top", ($(window).height() / 2 - LOADING_WRAPPER_HEIGHT));

	console.log("Window.height" + $(window).height());

}

function onDocumentMouseMove( event ) {

	mouseX = ( event.clientX - windowHalfX ) / 3;
	mouseY = ( event.clientY - windowHalfY ) / 3;

}

var audioBuffer;
var sourceNode;
function setupAudioNodes() {
	// setup a analyser
	analyser = context.createAnalyser();
	// create a buffer source node
	sourceNode = context.createBufferSource();
	//connect source to analyser as link
	sourceNode.connect(analyser);
	// and connect source to destination
	sourceNode.connect(context.destination);
}

function reset () {
	if (typeof sourceNode !== "undefined") {
		sourceNode.stop(0);
	}
	if (typeof microphone !== "undefined") {
		microphone = null;
	}
}

function updateVisualization () {

	// get the average, bincount is fftsize / 2
	if (fileChosen) {
		var array = new Uint8Array(analyser.frequencyBinCount);
		analyser.getByteFrequencyData(array);

		drawBars(array);
	}
	if (!isTouchDevice) {
		camera.position.x += ( mouseX - camera.position.x) * .05;
		//console.log("Camer pos x: " + camera.position.x);
		camera.position.y += ( - mouseY - camera.position.y) * .05;
		camera.lookAt( scene.position );
	}
	render();
	//renderer.render(scene, camera);

	rafID = window.requestAnimationFrame(updateVisualization);
	controls.update();
}

function render() {
	renderer.render( scene, camera );

}

function smoothenArr(array)
{
	var smooth_array = new Array(array.length);
	for (var i = 0; i<array.length-2; i++)
	smooth_array[i]=(array[i] + array[i+1])/2;

	smooth_array[array.length-1]=array[array.length-1];

	return smooth_array;
}

function drawBars (array) {

	//just show bins with a value over the treshold
	var threshold = 0;
	//the max count of bins for the visualization
	var maxBinCount = array.length;
	//space between bins
	var space = 3;

    var bass = Math.floor(array[1]); //1Hz Frequenz
	var snare = Math.floor(array[250]);
	//console.log("Array length " + array.length);
	console.log("BASS: " + bass);
    RADIUS = bass * .004;
	RADIUS = RADIUS < .75 ? .75 : RADIUS;
	//console.log("Radius:" + RADIUS);
	pivot.scale.x = RADIUS;
	pivot.scale.y = RADIUS;
	pivot.scale.z = RADIUS;

	//var dx = Math.random()*10;
  	//var dy = Math.random()*10;

	//cube_mesh.position.x = bass >= 255 ? dx : 0;
	//cube_mesh.position.y = bass >= 255 ? dy : 0;

	if (snare > 0) {
		if (snare > 100) {
			if (snare > 230) {
				cube_mesh.rotation.z +=  .01;
			}
			else {
				cube_mesh.rotation.z +=  .005;
			}
		} else {
			cube_mesh.rotation.z +=  .001;
		}
	}

	if (bass > 230) {
		cube_mesh.material.color.setHex( HIGHLIGHT_COLORS[Math.floor(Math.random() * HIGHLIGHT_COLORS.length)] );
		cube_mesh.position.z += 10;
		//cube_mesh.rotation.z +=  .01;
		if (cube_mesh.position.z >= 2800) {
			cube_mesh.position.z = 0;
		}
	} else {
		cube_mesh.material.color.setHex( HIGHLIGHT_COLORS[0] );
	}
	//cube_mesh.position.y = dy;
	//go over each bin
	for ( var i = 0; i < maxBinCount; i++ ){

	}
}

<commit_msg>touch improvements<commit_after>/*
 * Audiovisualization using the html canvas element.
 * Please note: this is an offline-only version. For this version to be used online you need an XMLHttpRequest to load the music file!
 * ©2017, Dominik Hofacker
 * https://www.behance.net/dominikhofacker
 * Please consider supporting this project on behance:
 * https://www.behance.net/gallery/49260123/Web-Audio-Visualization
 */

var rafID = null;
var analyser = null;
var c = null;
var cDraw = null;
var ctx = null;
var micropone = null;
var ctxDraw = null;

var loader;
var filename;
var renderer;
var skull;
var sunglasses;
var headphones;
var pivot;
var cube_mesh;
var scene;
var camera;
var controls;
var fileChosen = false;
var RADIUS = 2;
var mouseX = 0, mouseY = 0;
var windowHalfX = window.innerWidth / 2;
var windowHalfY = window.innerHeight / 2;
var loadedVar = 0, totalVar = 0;
var isTouchDevice = false;

const HIGHLIGHT_COLORS = [0x4200ff, 0x00ffff, 0xff0000, 0xff00ff];
const LOADING_WRAPPER_HEIGHT = 100;

//handle different prefix of the audio context
var AudioContext = AudioContext || webkitAudioContext;
//create the context.
var context = new AudioContext();

//using requestAnimationFrame instead of timeout...
if (!window.requestAnimationFrame)
	window.requestAnimationFrame = window.webkitRequestAnimationFrame;

$(function () {

		/*$(".inputfile + label, .button").addClass("animated fadeInUp");
		$('.inputfile + label, .button').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
                $('.inputfile + label, .button').removeClass('fadeInUp');
         });*/

		$('#loading_wrapper').css("top", ($(window).height() / 2 - LOADING_WRAPPER_HEIGHT));

		//handle different types navigator objects of different browsers
		navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia ||
	            navigator.mozGetUserMedia || navigator.msGetUserMedia;
	    //eigene Init
	    loader = new BufferLoader();
            //loader.visualize = visualize;
            //init canvas
	    initBinCanvas();
		//start updating
		rafID = window.requestAnimationFrame(updateVisualization);

});

function handleFiles(files) {
    if(files.length === 0){
        return;
    }
	fileChosen = true;
    setupAudioNodes();

	var fileReader  = new FileReader();
    fileReader.onload = function(){
         var arrayBuffer = this.result;
         console.log(arrayBuffer);
         console.log(arrayBuffer.byteLength);
     };
     fileReader.readAsArrayBuffer(files[0]);
     var url = URL.createObjectURL(files[0]);

	var request = new XMLHttpRequest();
	request.open('GET', url, true);
	request.responseType = 'arraybuffer';

 	// When loaded decode the data
	request.onload = function() {
		// decode the data
		context.decodeAudioData(request.response, function(buffer) {
		// when the audio is decoded play the sound
		sourceNode.buffer = buffer;
		sourceNode.start(0);
		//on error
		}, function(e) {
			console.log(e);
		});
	};
	request.send();

	camera.position.z = 375;
	camera.position.y = 0;

	$(".inputfile + label, .button").addClass("animated fadeOutDown");
	$("#viewer_discretion").html("Copyright 2017, Dominik Hofacker. All Rights Reserved.");
}

function playSample() {

	fileChosen = true;
    setupAudioNodes();

	var request = new XMLHttpRequest();
	request.open('GET', 'src/sample.mp3', true);
	request.responseType = 'arraybuffer';

 	// When loaded decode the data
	request.onload = function() {
		// decode the data
		context.decodeAudioData(request.response, function(buffer) {
		// when the audio is decoded play the sound
		sourceNode.buffer = buffer;
		sourceNode.start(0);
		//on error
		}, function(e) {
			console.log(e);
		});
	};
	request.send();

	camera.position.z = 375;
	camera.position.y = 0;

	$(".inputfile + label, .button").addClass("animated fadeOutDown");
	$("#viewer_discretion").html("You are listening to Jordan Schor - Cosmic (feat. Nathan Brumley).");
}

function initBinCanvas () {

	//SCENE#######################################################################
	scene = new THREE.Scene();
	scene.background = new THREE.Color( 0x00000);
	camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100000);
	renderer = new THREE.WebGLRenderer();
	renderer.setSize(window.innerWidth, window.innerHeight);
	renderer.setPixelRatio( window.devicePixelRatio );
	document.body.appendChild(renderer.domElement);

	controls = new THREE.OrbitControls( camera, renderer.domElement );

	controls.enabled = false;

	var geometry = new THREE.BoxGeometry(560, 560, 100000, 15, 55, 100);

	var cubeMat = new THREE.MeshBasicMaterial({color: '#4200ff', wireframe: true});
	var testMat = new THREE.MeshStandardMaterial({
		roughness: 0,
		color: 'white'
	});
	cube_mesh = new THREE.Mesh(geometry, cubeMat);
	scene.add(cube_mesh);

	//MATERIALS###################################################################
	var material = new THREE.MeshBasicMaterial({vertexColors: THREE.VertexColors});
	var objMat = new THREE.MeshStandardMaterial({
		color: 'yellow',
		wireframe: true,
		roughness: .3
	});
	var newMat = new THREE.MeshPhongMaterial({
		color: '#6e3bff'
	});
	var headphoneMat = new THREE.MeshStandardMaterial({
		roughness: 0.4,
		color: 'yellow',
		wireframe: false
	});
	var box = new THREE.Box3();
	pivot = new THREE.Group(); //group objs and center pivot of skull

	//LOADING OBJs#################################################################
	var manager = new THREE.LoadingManager();
		manager.onProgress = function ( item, loaded, total ) {
			console.log( item, loaded, total ); //loaded 1 total 3
			loadedVar = loaded;
			totalVar = total;
	};
	var onProgress = function ( xhr ) {
		if ( xhr.lengthComputable ) {
			var percentComplete = xhr.loaded / xhr.total * 100;
			console.log( Math.round(percentComplete, 2) + '% loaded' );
			$(".label").html(Math.round(percentComplete, 2) + '% loaded');
			//console.log("LOADED VAR:" + loadedVar + "\n TOTAL VAR:" + totalVar);
			if (percentComplete === 100 && loadedVar === 2) {
				$(".inputfile + label, .button").css("visibility", "visible");
				$(".inputfile + label, .button").addClass("animated fadeInUp");
				$('.inputfile + label, .button').one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
						$('.inputfile + label, .button').removeClass('fadeInUp');
				});
				$("#loading_screen").addClass("animated fadeOut");
				$("#loading_screen").one('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
						$("#loading_screen").css("display", "none");
						$("#viewer_discretion").html("This music visualizer may be harmful to viewers who are prone to epilepsy. Viewer discretion is advised.");
				});
			}
		}
	};
	var onError = function ( xhr ) {
	};
	var loader3D = new THREE.OBJLoader( manager );
	// load a resource
	loader3D.load(
		// resource URL
		'src/skull_lowpoly.obj',
		// Function when resource is loaded
		function ( object ) {
			object.traverse(function (child) {
				if ( child instanceof THREE.Mesh ) {

						child.material = newMat;
						child.shading = THREE.FlatShading;

					}
			});
			object.position.y = 0;
			object.position.x = 0;
			box.setFromObject( object );
			box.getCenter( object.position ); // this re-sets the mesh position
			object.position.multiplyScalar( - 1 );
			pivot.add( object );
			skull = object;
	}, onProgress, onError );

	loader3D.load(
		'src/sunglasses.obj',
		function ( object ) {
			object.traverse(function( child ) {
				if (child instanceof THREE.Mesh) {
					child.material = objMat;
					child.shading = THREE.SmoothShading;
				}
			});
			object.position.x = 0;
			object.position.y = -3;
			object.position.z = 10;
			pivot.add( object );
			sunglasses = object;
	}, onProgress, onError );

	loader3D.load(
		'src/headphones.obj',
		function ( object ) {
			object.traverse(function( child ) {
				if (child instanceof THREE.Mesh) {
					child.material = headphoneMat;
					child.shading = THREE.SmoothShading;
				}
			});
			object.position.x = 0;
			object.position.y = 3;
			object.position.z = 30;
			pivot.add( object );
			headphones = object;
	}, onProgress, onError );

	scene.add( pivot );

	//scene.add(skull);
	//camera.position.z = 375;
	camera.position.z = 500;
	camera.position.y = 500;

	var ambient = new THREE.AmbientLight( 0x101030 );
	scene.add( ambient );

	var directionalLight = new THREE.DirectionalLight( 0xffeedd );
	directionalLight.position.set( 0, 0, 1 );
	scene.add( directionalLight );

	document.addEventListener( 'mousemove', onDocumentMouseMove, false );
	window.addEventListener( 'resize', onWindowResize, false );
	window.addEventListener('touchstart', function() {
	  // the user touched the screen!
		isTouchDevice = true;
		controls.addEventListener( 'change', render ); // remove when using animation loop
		// enable animation loop when using damping or autorotation
		controls.enableDamping = true;
		controls.dampingFactor = 0.5;
		controls.enableZoom = false;
		controls.noPan = true;
		// How far you can orbit vertically, upper and lower limits.
		// Range is 0 to Math.PI radians.
		controls.minPolarAngle = 0.35 * Math.PI; // radians
		controls.maxPolarAngle = 0.65 * Math.PI; // radians

		// How far you can orbit horizontally, upper and lower limits.
		// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
		controls.minAzimuthAngle = - Math.PI / 5.5; // radians
		controls.maxAzimuthAngle = Math.PI / 5.5; // radians
		controls.enabled = true;
	});

}

function onWindowResize() {

	windowHalfX = window.innerWidth / 2;
	windowHalfY = window.innerHeight / 2;

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

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

	$('#loading_wrapper').css("top", ($(window).height() / 2 - LOADING_WRAPPER_HEIGHT));

	console.log("Window.height" + $(window).height());

}

function onDocumentMouseMove( event ) {

	mouseX = ( event.clientX - windowHalfX ) / 3;
	mouseY = ( event.clientY - windowHalfY ) / 3;

}

var audioBuffer;
var sourceNode;
function setupAudioNodes() {
	// setup a analyser
	analyser = context.createAnalyser();
	// create a buffer source node
	sourceNode = context.createBufferSource();
	//connect source to analyser as link
	sourceNode.connect(analyser);
	// and connect source to destination
	sourceNode.connect(context.destination);
}

function reset () {
	if (typeof sourceNode !== "undefined") {
		sourceNode.stop(0);
	}
	if (typeof microphone !== "undefined") {
		microphone = null;
	}
}

function updateVisualization () {

	// get the average, bincount is fftsize / 2
	if (fileChosen) {
		var array = new Uint8Array(analyser.frequencyBinCount);
		analyser.getByteFrequencyData(array);

		drawBars(array);
	}
	if (!isTouchDevice) {
		camera.position.x += ( mouseX - camera.position.x) * .05;
		//console.log("Camer pos x: " + camera.position.x);
		camera.position.y += ( - mouseY - camera.position.y) * .05;
		camera.lookAt( scene.position );
	}
	render();
	//renderer.render(scene, camera);

	rafID = window.requestAnimationFrame(updateVisualization);
	controls.update();
}

function render() {
	renderer.render( scene, camera );

}

function smoothenArr(array)
{
	var smooth_array = new Array(array.length);
	for (var i = 0; i<array.length-2; i++)
	smooth_array[i]=(array[i] + array[i+1])/2;

	smooth_array[array.length-1]=array[array.length-1];

	return smooth_array;
}

function drawBars (array) {

	//just show bins with a value over the treshold
	var threshold = 0;
	//the max count of bins for the visualization
	var maxBinCount = array.length;
	//space between bins
	var space = 3;

    var bass = Math.floor(array[1]); //1Hz Frequenz
	var snare = Math.floor(array[250]);
	//console.log("Array length " + array.length);
	console.log("BASS: " + bass);
    RADIUS = bass * .004;
	RADIUS = RADIUS < .75 ? .75 : RADIUS;
	//console.log("Radius:" + RADIUS);
	pivot.scale.x = RADIUS;
	pivot.scale.y = RADIUS;
	pivot.scale.z = RADIUS;

	//var dx = Math.random()*10;
  	//var dy = Math.random()*10;

	//cube_mesh.position.x = bass >= 255 ? dx : 0;
	//cube_mesh.position.y = bass >= 255 ? dy : 0;

	if (snare > 0) {
		if (snare > 100) {
			if (snare > 230) {
				cube_mesh.rotation.z +=  .01;
			}
			else {
				cube_mesh.rotation.z +=  .005;
			}
		} else {
			cube_mesh.rotation.z +=  .001;
		}
	}

	if (bass > 230) {
		cube_mesh.material.color.setHex( HIGHLIGHT_COLORS[Math.floor(Math.random() * HIGHLIGHT_COLORS.length)] );
		cube_mesh.position.z += 10;
		//cube_mesh.rotation.z +=  .01;
		if (cube_mesh.position.z >= 2800) {
			cube_mesh.position.z = 0;
		}
	} else {
		cube_mesh.material.color.setHex( HIGHLIGHT_COLORS[0] );
	}
	//cube_mesh.position.y = dy;
	//go over each bin
	for ( var i = 0; i < maxBinCount; i++ ){

	}
}

<|endoftext|>


<commit_before>// All helper methods for all views.
var helpInfo = require('./helpInfo.js');

module.exports = {
    activeLink: (href, current) => {
        current = (current == '/home' ? '/' : current);
        if ((href === '/' && current === href) || (href !== '/' && current.startsWith(href))) {
            return 'active';
        }
    },
    limit: (string, maxLength) => {
        return (string.length > maxLength ? string.substring(0, maxLength) + '...' : string);
    },
    isTeacher: (camp, user) => camp.teachers.map(t => t._id).includes(user._id),
    isInvolved: (involvements, camp) => involvements.map(i => i.camp._id.toString()).includes(camp._id.toString()), // Not sure why I need toString() but I do
    rank: (involvements, camp) => {
        const possible = involvements.filter(i => i.camp._id.toString() == camp._id.toString());
        return (possible.length > 0 ? possible[0].rank : null);
    },
    getRankFromCamp: (camp, user) => {
        let rank = null;
        if (camp.ambassador && (camp.ambassador == user._id || camp.ambassador._id == user._id)) rank = 'ambassador';
        if (camp.director && (camp.director == user._id || camp.director._id == user._id)) rank = 'director';
        if (camp.teachers.includes(user._id) || camp.teachers.map(t => t._id).includes(user._id)) rank = 'teacher';
        return rank;
    },
    getHelpInfo: label => {
        if (label in helpInfo) {
            return helpInfo[label];
        } else {
            return 'No help info found!'
        }
    }
};<commit_msg>Code fixes<commit_after>// All helper methods for all views.
var helpInfo = require('./helpInfo.js');

module.exports = {
    activeLink: (href, current) => {
        current = (current == '/home' ? '/' : current);
        if ((href === '/' && current === href) || (href !== '/' && current.startsWith(href))) {
            return 'active';
        }
    },
    limit: (string, maxLength) => {
        return (string.length > maxLength ? string.substring(0, maxLength) + '...' : string);
    },
    isTeacher: (camp, user) => camp.teachers.map(t => t._id).includes(user._id),
    isInvolved: (involvements, camp) => involvements.map(i => i.camp.id).includes(camp.id), // Not sure why I need toString() but I do
    rank: (involvements, camp) => {
        const possible = involvements.filter(i => i.camp.id == camp.id);
        return (possible.length > 0 ? possible[0].rank : null);
    },
    getRankFromCamp: (camp, user) => {
        let rank = null;
        if (camp.ambassador && (camp.ambassador == user._id || camp.ambassador._id == user._id)) rank = 'ambassador';
        if (camp.director && (camp.director == user._id || camp.director._id == user._id)) rank = 'director';
        if (camp.teachers.includes(user._id) || camp.teachers.map(t => t._id).includes(user._id)) rank = 'teacher';
        return rank;
    },
    getHelpInfo: label => {
        if (label in helpInfo) {
            return helpInfo[label];
        } else {
            return 'No help info found!'
        }
    }
};<|endoftext|>


<commit_before>(function () {
    'use strict';

    var conf = {};

    // Init functions, called on DOMContentLoaded event
    conf.init = function () {
        conf.map.init($('#map-canvas'));
        conf.menu.init();
    }

    /***
        Google Maps implementation
    ***/
    conf.map = {
        marker: 'themes/yellow-swan/img/marker-default.png'
    }

    // Google Maps configs
    conf.map.init = function ($element) {
        conf.map.element = $element;

        conf.map.geocoder = new google.maps.Geocoder();

        conf.map.latlng = new google.maps.LatLng(0, 0),
            conf.map.options = {
                zoom: 16,
                center: conf.map.latlng,
                scrollwheel: false,
                streetViewControl: true,
                labels: true,
                mapTypeId: google.maps.MapTypeId.ROADMAP
            };

        conf.map.canvas = new google.maps.Map(conf.map.element.get(0), conf.map.options);
        conf.map.canvas.setCenter(conf.map.latlng);

        conf.map.createMarker();
    };

    conf.map.createMarker = function () {

        conf.map.address = conf.map.element.attr('data-address');

        conf.map.geocoder.geocode({ 'address': conf.map.address}, function (results, status) {

            if (status === google.maps.GeocoderStatus.OK) {

                conf.map.canvas.setCenter(results[0].geometry.location);

                new google.maps.Marker({
                    map: conf.map.canvas,
                    position: results[0].geometry.location,
                    icon: conf.map.marker
                });
            } else {
                if (console) {
                    console.log('Google Maps was not loaded: ', status);
                }
            }
        });
    };

    /***
        Create animated scroll for menu links
    ***/
    conf.menu = {
        itemsSelector: '.nav-link',
        animationSpeed: 400,
    }

    conf.menu.init = function () {
        conf.menu.menuItems = $(conf.menu.itemsSelector);
        conf.menu.document = $('html, body');

        conf.menu.menuItems.on('click.animateScroll', function (event) {
            event.preventDefault();

            conf.menu.animateTo(event.target);
        });
    }

    conf.menu.animateTo = function ($link) {

        var $link = $($link),
            href, offSetTop;

        if (!conf.menu.isAValidLink($link)) {
            return false;
        }

        href = $link.attr('href'),
        offSetTop = $(href).offset().top;

        conf.menu.document.finish().animate({scrollTop : offSetTop}, conf.menu.animationSpeed, function () {
            location.hash = href;
        });
    }

    conf.menu.isAValidLink = function ($link) {
        return !($link.attr('href')[0] !== '#');
    }

    $(function () {
        // call all functions
        conf.init();
    });
}());<commit_msg>Code refactor<commit_after>(function () {
    'use strict';

    var conf = {};

    // Init functions, called on DOMContentLoaded event
    conf.init = function () {
        conf.map.init($('#map-canvas'));
        conf.menu.init();
    };

    /***
        Google Maps implementation
    ***/
    conf.map = {
        marker: 'themes/yellow-swan/img/marker-default.png'
    };

    // Google Maps configs
    conf.map.init = function ($element) {
        conf.map.element = $element;

        conf.map.geocoder = new google.maps.Geocoder();

        conf.map.latlng = new google.maps.LatLng(0, 0);

        conf.map.options = {
            zoom: 16,
            center: conf.map.latlng,
            scrollwheel: false,
            streetViewControl: true,
            labels: true,
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        conf.map.canvas = new google.maps.Map(conf.map.element.get(0), conf.map.options);
        conf.map.canvas.setCenter(conf.map.latlng);

        conf.map.createMarker();
    };

    conf.map.createMarker = function () {

        conf.map.address = conf.map.element.attr('data-address');

        conf.map.geocoder.geocode({ 'address': conf.map.address}, function (results, status) {

            if (status === google.maps.GeocoderStatus.OK) {

                conf.map.canvas.setCenter(results[0].geometry.location);

                new google.maps.Marker({
                    map: conf.map.canvas,
                    position: results[0].geometry.location,
                    icon: conf.map.marker
                });
            } else {
                if (window.console) {
                    console.log('Google Maps was not loaded: ', status);
                }
            }
        });
    };

    /***
        Create animated scroll for menu links
    ***/
    conf.menu = {
        itemsSelector: '.nav-link[href^="#"]',
        animationSpeed: 400
    };

    conf.menu.init = function () {
        conf.menu.menuItems = $(conf.menu.itemsSelector);
        conf.menu.document = $('html, body');

        conf.menu.menuItems.on('click.animateScroll', function (event) {
            event.preventDefault();

            conf.menu.animateTo(event.target);
        });
    };

    conf.menu.animateTo = function (link) {

        var $link = $(link),
            href = $link.attr('href'),
            offSetTop = $(href).offset().top;

        conf.menu.document.finish().animate({scrollTop : offSetTop}, conf.menu.animationSpeed, function () {
            location.hash = href;
        });
    };

    conf.init();
}());<|endoftext|>


<commit_before>var _ = require('underscore');
var upnp = require("./lib/upnp.js");
var binary = require('binary');
var xml2js = require('xml2js');
var xmlParser = new xml2js.Parser({explicitArray: false});
var path = require('path');
var fs = require('fs');

var config = require('../config.js');

var minimUriProcessor = function(prefix) {
    return function (uri) {
        return path.join(prefix, decodeURI(uri.replace(/http:.*\/minimserver\/\*\/[^\/.]*\//, '').replace(/\*/g,'%')));
    };
}
var trackProcessor = minimUriProcessor(config.musicRoot);

var writeM3u = function (tracks, playlistName) {
    var playlistLocation = path.normalize(config.playlistPath);
    var data = '';
    _.each(tracks, function(track) {
        var relTrack = path.relative(playlistLocation, track);
        data += relTrack + '\n';
    });
    var playlistFile = path.join(playlistLocation, playlistName + '.m3u');
    fs.writeFile(playlistFile, data, {flag: 'wx'});
};

var processReadListResponse = function (device, callback) {
    return function(res) {
        var body = '';
        res.setEncoding('utf8');
        res.on('data', function (chunk) {
            body += chunk;
        });
        res.on('end', function () {
            xmlParser.parseString(body, function (err, result) {
                xmlParser.parseString(result['s:Envelope']['s:Body']['u:ReadListResponse'].TrackList, function (err, result) {
                    var tracks = [];
                    if (_.isArray(result.TrackList.Entry)) {
                        _.each(result.TrackList.Entry, function (track) {
                            tracks.push(trackProcessor(track.Uri));
                        });
                    } else {
                        if (result.TrackList.Entry) {
                            tracks.push(trackProcessor(result.TrackList.Entry.Uri));
                        }
                    }
                    callback(tracks);
                });
            });
        });
    };
};

var generatePlaylist = function (device, idArray, playlistName) {
    var idArrayString = '';
    _.each(idArray, function (id) {
        idArrayString += (id + ' ');
    });
    var storePlaylist = function (tracks) {
        writeM3u(tracks, playlistName);
    };
    upnp.soapRequest(
        device,
        'Ds/Playlist',
        'urn:av-openhome.org:service:Playlist:1',
        'ReadList',
        '<IdList>'+idArrayString+'</IdList>',
        processReadListResponse(device, storePlaylist)
    );
}

var processPlaylistResponse = function (device, playlistName) {
    return function(res) {
        var body = '';
        res.setEncoding('utf8');
        res.on('data', function (chunk) {
            body += chunk;
        });
        res.on('end', function () {
            xmlParser.parseString(body, function (err, result) {
                var buffer = new Buffer(result['s:Envelope']['s:Body']['u:IdArrayResponse'].Array, 'base64');
                var arrayList = [];
                var binaryList = binary.parse(buffer);
                _.each(_.range(buffer.length / 4), function () {
                    arrayList.push(binaryList.word32bu('a').vars.a);
                });
                generatePlaylist(device, arrayList, playlistName);
            });
        });
    };
};

var savePlaylist = function (device, playlistName) {
    upnp.soapRequest(
        device,
        'Ds/Playlist',
        'urn:av-openhome.org:service:Playlist:1',
        'IdArray',
        '',
        processPlaylistResponse(device, playlistName)
    );
}
exports.savePlaylist = savePlaylist;<commit_msg>Fixed bug where URI not properly converted<commit_after>var _ = require('underscore');
var upnp = require("./lib/upnp.js");
var binary = require('binary');
var xml2js = require('xml2js');
var xmlParser = new xml2js.Parser({explicitArray: false});
var path = require('path');
var fs = require('fs');

var config = require('../config.js');

var minimUriProcessor = function(prefix) {
    return function (uri) {
        return path.join(prefix, decodeURIComponent(uri.replace(/http:.*\/minimserver\/\*\/[^\/.]*\//, '').replace(/\*/g,'%')));
    };
}
var trackProcessor = minimUriProcessor(config.musicRoot);

var writeM3u = function (tracks, playlistName) {
    var playlistLocation = path.normalize(config.playlistPath);
    var data = '';
    _.each(tracks, function(track) {
        var relTrack = path.relative(playlistLocation, track);
        data += relTrack + '\n';
    });
    var playlistFile = path.join(playlistLocation, playlistName + '.m3u');
    fs.writeFile(playlistFile, data, {flag: 'wx'});
};

var processReadListResponse = function (device, callback) {
    return function(res) {
        var body = '';
        res.setEncoding('utf8');
        res.on('data', function (chunk) {
            body += chunk;
        });
        res.on('end', function () {
            xmlParser.parseString(body, function (err, result) {
                xmlParser.parseString(result['s:Envelope']['s:Body']['u:ReadListResponse'].TrackList, function (err, result) {
                    var tracks = [];
                    if (_.isArray(result.TrackList.Entry)) {
                        _.each(result.TrackList.Entry, function (track) {
                            tracks.push(trackProcessor(track.Uri));
                        });
                    } else {
                        if (result.TrackList.Entry) {
                            tracks.push(trackProcessor(result.TrackList.Entry.Uri));
                        }
                    }
                    callback(tracks);
                });
            });
        });
    };
};

var generatePlaylist = function (device, idArray, playlistName) {
    var idArrayString = '';
    _.each(idArray, function (id) {
        idArrayString += (id + ' ');
    });
    var storePlaylist = function (tracks) {
        writeM3u(tracks, playlistName);
    };
    upnp.soapRequest(
        device,
        'Ds/Playlist',
        'urn:av-openhome.org:service:Playlist:1',
        'ReadList',
        '<IdList>'+idArrayString+'</IdList>',
        processReadListResponse(device, storePlaylist)
    );
}

var processPlaylistResponse = function (device, playlistName) {
    return function(res) {
        var body = '';
        res.setEncoding('utf8');
        res.on('data', function (chunk) {
            body += chunk;
        });
        res.on('end', function () {
            xmlParser.parseString(body, function (err, result) {
                var buffer = new Buffer(result['s:Envelope']['s:Body']['u:IdArrayResponse'].Array, 'base64');
                var arrayList = [];
                var binaryList = binary.parse(buffer);
                _.each(_.range(buffer.length / 4), function () {
                    arrayList.push(binaryList.word32bu('a').vars.a);
                });
                generatePlaylist(device, arrayList, playlistName);
            });
        });
    };
};

var savePlaylist = function (device, playlistName) {
    upnp.soapRequest(
        device,
        'Ds/Playlist',
        'urn:av-openhome.org:service:Playlist:1',
        'IdArray',
        '',
        processPlaylistResponse(device, playlistName)
    );
}
exports.savePlaylist = savePlaylist;
<|endoftext|>


<commit_before>define(["Award", "Book", "InitialState", "Nomination", "process/Action", "process/Reducer"], function(Award, Book, InitialState, Nomination, Action, Reducer)
{
    "use strict";
    QUnit.module("Reducer");

    QUnit.test("addBook()", function(assert)
    {
        // Setup.
        var state = new InitialState();
        assert.equal(state.books.length, 68);
        var book = createBook1();
        var action = Action.addBook(book);

        // Run.
        var result = Reducer.root(state, action);

        // Verify.
        assert.ok(result);
        assert.equal(result.books.length, 69);
    });

    QUnit.test("addNomination()", function(assert)
    {
        // Setup.
        var state = new InitialState();
        assert.equal(state.books.length, 68);
        var book = createBook1();
        state = Reducer.root(state, Action.addBook(book));
        var nomination = createNomination1();
        var action = Action.addNomination(book, nomination);

        // Run.
        var result = Reducer.root(state, action);

        // Verify.
        assert.ok(result);
        assert.equal(result.books.length, 69);
        var nominations = result.bookToNomination[book];
        assert.equal(nominations.length, 1);
        assert.equal(nominations[0], nomination);
    });

    function createBook1()
    {
        var title = "A Dark and Stormy Night";
        var author = "Noah Boddy";

        return new Book(title, author);
    }

    function createNomination1()
    {
        var awardKey = Award.AGATHA;
        var award = Award.properties[awardKey];
        var categoryKey = award.categories.CONTEMPORARY;
        var category = award.categories.properties[categoryKey];
        var year = 2016;

        return new Nomination(award, category, year);
    }

    // function createNomination1()
    // {
    //     // var title = "A Dark and Stormy Night";
    //     // var author = "Noah Boddy";
    //     var awardKey = Award.AGATHA;
    //     var award = Award.properties[awardKey];
    //     var categoryKey = award.categories.CONTEMPORARY;
    //     var category = award.categories.properties[categoryKey];
    //     var year = 2016;
    //
    //     return new Nomination(award, category, year);
    // }
});
<commit_msg>Added Crime & Beyond books.<commit_after>define(["Award", "Book", "InitialState", "Nomination", "process/Action", "process/Reducer"], function(Award, Book, InitialState, Nomination, Action, Reducer)
{
    "use strict";
    QUnit.module("Reducer");

    QUnit.test("addBook()", function(assert)
    {
        // Setup.
        var state = new InitialState();
        assert.equal(state.books.length, 85);
        var book = createBook1();
        var action = Action.addBook(book);

        // Run.
        var result = Reducer.root(state, action);

        // Verify.
        assert.ok(result);
        assert.equal(result.books.length, 86);
    });

    QUnit.test("addNomination()", function(assert)
    {
        // Setup.
        var state = new InitialState();
        assert.equal(state.books.length, 85);
        var book = createBook1();
        state = Reducer.root(state, Action.addBook(book));
        var nomination = createNomination1();
        var action = Action.addNomination(book, nomination);

        // Run.
        var result = Reducer.root(state, action);

        // Verify.
        assert.ok(result);
        assert.equal(result.books.length, 86);
        var nominations = result.bookToNomination[book];
        assert.equal(nominations.length, 1);
        assert.equal(nominations[0], nomination);
    });

    function createBook1()
    {
        var title = "A Dark and Stormy Night";
        var author = "Noah Boddy";

        return new Book(title, author);
    }

    function createNomination1()
    {
        var awardKey = Award.AGATHA;
        var award = Award.properties[awardKey];
        var categoryKey = award.categories.CONTEMPORARY;
        var category = award.categories.properties[categoryKey];
        var year = 2016;

        return new Nomination(award, category, year);
    }

    // function createNomination1()
    // {
    //     // var title = "A Dark and Stormy Night";
    //     // var author = "Noah Boddy";
    //     var awardKey = Award.AGATHA;
    //     var award = Award.properties[awardKey];
    //     var categoryKey = award.categories.CONTEMPORARY;
    //     var category = award.categories.properties[categoryKey];
    //     var year = 2016;
    //
    //     return new Nomination(award, category, year);
    // }
});
<|endoftext|>


<commit_before>import {
  getResourcePageLink, getUnitAddress, getResourceDistance, getPrice
} from '../utils';

describe('domain resource utility function', () => {
  describe('getResourcePageLink', () => {
    const resource = {
      id: 'foo'
    };

    test('should return link with resource as part of url', () => {
      const link = getResourcePageLink(resource);
      expect(link).toContain('/resources/');
    });

    test('should return link with resource id', () => {
      const link = getResourcePageLink(resource);
      expect(link).toContain(resource.id);
    });

    test('should be fine even when resource has no id', () => {
      const link = getResourcePageLink({});
      expect(link).toBeDefined();
    });

    test('can append more query to the link', () => {
      const link = getResourcePageLink(resource, 'foo=bar');
      expect(link).toContain('bar');
    });
  });

  describe('getUnitAddress', () => {
    const unit = {
      street_address: {
        en: 'foo',
        fi: 'bar'
      },
    };
    const mockLocale = 'en';
    test('get street address from unit by locale, contain no address zipcode or municiple if none', () => {
      const address = getUnitAddress(unit, mockLocale);

      expect(address).toEqual(unit.street_address.en);
    });

    test('return empty string even when unit have no data', () => {
      const address = getUnitAddress({}, mockLocale);
      expect(address).toBeDefined();
    });

    test('get street address from unit by locale, ', () => {
      const address = getUnitAddress(unit, mockLocale);

      expect(address).toEqual(unit.street_address.en);
    });

    test('return address zip if exist', () => {
      const address = getUnitAddress({ ...unit, address_zip: '1234' });
      expect(address).toContain('1234');
    });

    test('return municipality if exist', () => {
      const address = getUnitAddress({ ...unit, municipality: 'espoo' });
      expect(address).toContain('espoo');
    });
  });

  describe('getResourceDistance', () => {
    const resource = {
      distance: 2000
    };
    const distance = getResourceDistance(resource);

    test('return distance to contain km in string', () => {
      expect(distance).toContain('km');
    });

    test('return null string in case no distance', () => {
      const emptyDistance = getResourceDistance({});
      expect(emptyDistance).toBeDefined();
    });

    test('round result to 1 digit after comma', () => {
      expect(distance).toEqual('2 km');
      const rounded = getResourceDistance({ distance: 100 });
      expect(rounded).toEqual('0.1 km');
    });
  });

  describe('getPrice', () => {
    const fakeT = foo => foo;
    test('return free text if there is no price', () => {
      const price = getPrice({}, fakeT);
      expect(price).toContain('free');
    });

    test('return free text if price is 0', () => {
      const price = getPrice({
        min_price_per_hour: '0'
      }, fakeT);

      expect(price).toContain('free');
    });

    test('return price even if there is 1 price', () => {
      const price = getPrice({
        min_price_per_hour: '123'
      }, fakeT);

      expect(price).toContain('123');
    });

    test('return price range if both min and max price included', () => {
      const price = getPrice({
        min_price_per_hour: '123',
        max_price_per_hour: '234'
      }, fakeT);

      expect(price).toEqual('123 - 234 €/h');
    });

    test('return null if price exist but not number', () => {
      const price = getPrice({
        min_price_per_hour: 'foo'
      });
      expect(price).toBeNull();
    });
  });
});
<commit_msg>add test for isFree<commit_after>import {
  getResourcePageLink, getUnitAddress, getResourceDistance, getPrice, isFree
} from '../utils';

describe('domain resource utility function', () => {
  describe('getResourcePageLink', () => {
    const resource = {
      id: 'foo'
    };

    test('should return link with resource as part of url', () => {
      const link = getResourcePageLink(resource);
      expect(link).toContain('/resources/');
    });

    test('should return link with resource id', () => {
      const link = getResourcePageLink(resource);
      expect(link).toContain(resource.id);
    });

    test('should be fine even when resource has no id', () => {
      const link = getResourcePageLink({});
      expect(link).toBeDefined();
    });

    test('can append more query to the link', () => {
      const link = getResourcePageLink(resource, 'foo=bar');
      expect(link).toContain('bar');
    });
  });

  describe('getUnitAddress', () => {
    const unit = {
      street_address: {
        en: 'foo',
        fi: 'bar'
      },
    };
    const mockLocale = 'en';
    test('get street address from unit by locale, contain no address zipcode or municiple if none', () => {
      const address = getUnitAddress(unit, mockLocale);

      expect(address).toEqual(unit.street_address.en);
    });

    test('return empty string even when unit have no data', () => {
      const address = getUnitAddress({}, mockLocale);
      expect(address).toBeDefined();
    });

    test('get street address from unit by locale, ', () => {
      const address = getUnitAddress(unit, mockLocale);

      expect(address).toEqual(unit.street_address.en);
    });

    test('return address zip if exist', () => {
      const address = getUnitAddress({ ...unit, address_zip: '1234' });
      expect(address).toContain('1234');
    });

    test('return municipality if exist', () => {
      const address = getUnitAddress({ ...unit, municipality: 'espoo' });
      expect(address).toContain('espoo');
    });
  });

  describe('getResourceDistance', () => {
    const resource = {
      distance: 2000
    };
    const distance = getResourceDistance(resource);

    test('return distance to contain km in string', () => {
      expect(distance).toContain('km');
    });

    test('return null string in case no distance', () => {
      const emptyDistance = getResourceDistance({});
      expect(emptyDistance).toBeDefined();
    });

    test('round result to 1 digit after comma', () => {
      expect(distance).toEqual('2 km');
      const rounded = getResourceDistance({ distance: 100 });
      expect(rounded).toEqual('0.1 km');
    });
  });

  describe('getPrice', () => {
    const fakeT = foo => foo;
    test('return free text if there is no price', () => {
      const price = getPrice({}, fakeT);
      expect(price).toContain('free');
    });

    test('return free text if price is 0', () => {
      const price = getPrice({
        min_price_per_hour: '0'
      }, fakeT);

      expect(price).toContain('free');
    });

    test('return price even if there is 1 price', () => {
      const price = getPrice({
        min_price_per_hour: '123'
      }, fakeT);

      expect(price).toContain('123');
    });

    test('return price range if both min and max price included', () => {
      const price = getPrice({
        min_price_per_hour: '123',
        max_price_per_hour: '234'
      }, fakeT);

      expect(price).toEqual('123 - 234 €/h');
    });

    test('return null if price exist but not number', () => {
      const price = getPrice({
        min_price_per_hour: 'foo'
      });
      expect(price).toBeNull();
    });
  });

  describe('isFree', () => {
    test('return true if there is no price', () => {
      const price = isFree({});
      expect(price).toBeTruthy();
    });

    test('return true if price is 0', () => {
      const price = isFree({
        min_price_per_hour: '0'
      });

      expect(price).toBeTruthy();
    });

    test('return false even if there is 1 price', () => {
      const price = isFree({
        min_price_per_hour: '123'
      });

      expect(price).toBeFalsy();
    });

    test('return false if both min and max price included', () => {
      const price = isFree({
        min_price_per_hour: '123',
        max_price_per_hour: '234'
      });

      expect(price).toBeFalsy();
    });
  });
});
<|endoftext|>


<commit_before>//Lets require/import the HTTP module
var fs = require("fs"),
	express = require("express"),
	path = require("path");

function createWebViewer(){
	var app = express(),
		userHandler = {
			file: null,
			dir: null
		};

	app.all("*", function(req, res, next){
		if (!req.query.path) {
			res.sendStatus(404);
			return;
		}
		req.query.path = path.normalize(req.query.path);
		next();
	});

	app.get("/view", function(req, res, next){
		// basic information
		res.locals.path = req.query.path;
		res.locals.dirname = path.dirname(req.query.path),
		res.locals.basename = path.basename(req.query.path);

		// read parent dir
		if (res.locals.dirname == req.query.path) {
			next();
			return;
		}
		fs.readdir(res.locals.dirname, function(err, files){
			if (err) {
				next();
				return;
			}
			var i = files.indexOf(res.locals.basename);

			res.locals.prev = files[i - 1];
			res.locals.next = files[i + 1];

			next();
		});
	}, function(req, res) {
		// route to file or dir
		fs.stat(req.query.path, function(err, stat){
			if (err) {
				res.sendStatus(404);
				return;
			}
			if (stat.isFile()) {
				viewFile(req, res);
			} else {
				viewDir(req, res);
			}
		});
	});

	app.get("/src", function(req, res) {
		fs.stat(req.query.path, function(err, stat){
			if (err || !stat.isFile()) {
				res.sendStatus(404);
			} else {
				res.sendFile(req.query.path);
			}
		});
	});

	function viewFile(req, res) {
		userHandler.file(req, res);
	}

	function viewDir(req, res) {
		// read dir
		fs.readdir(req.query.path, function(err, files) {
			if (err) {
				res.sendStatus(500);
				return;
			}
			res.locals.files = files;
			userHandler.dir(req, res);
		});
	}

	function file(callback) {
		userHandler.file = callback;
	}

	function dir() {
		userHandler.dir = callback;
	}

	function listen() {
		app.listen.apply(app, arguments);
	}

	return {
		file: file,
		dir: dir,
		listen: listen
	};
}

module.export = createWebViewer;
<commit_msg>Remove comment<commit_after>var fs = require("fs"),
	express = require("express"),
	path = require("path");

function createWebViewer(){
	var app = express(),
		userHandler = {
			file: null,
			dir: null
		};

	app.all("*", function(req, res, next){
		if (!req.query.path) {
			res.sendStatus(404);
			return;
		}
		req.query.path = path.normalize(req.query.path);
		next();
	});

	app.get("/view", function(req, res, next){
		// basic information
		res.locals.path = req.query.path;
		res.locals.dirname = path.dirname(req.query.path),
		res.locals.basename = path.basename(req.query.path);

		// read parent dir
		if (res.locals.dirname == req.query.path) {
			next();
			return;
		}
		fs.readdir(res.locals.dirname, function(err, files){
			if (err) {
				next();
				return;
			}
			var i = files.indexOf(res.locals.basename);

			res.locals.prev = files[i - 1];
			res.locals.next = files[i + 1];

			next();
		});
	}, function(req, res) {
		// route to file or dir
		fs.stat(req.query.path, function(err, stat){
			if (err) {
				res.sendStatus(404);
				return;
			}
			if (stat.isFile()) {
				viewFile(req, res);
			} else {
				viewDir(req, res);
			}
		});
	});

	app.get("/src", function(req, res) {
		fs.stat(req.query.path, function(err, stat){
			if (err || !stat.isFile()) {
				res.sendStatus(404);
			} else {
				res.sendFile(req.query.path);
			}
		});
	});

	function viewFile(req, res) {
		userHandler.file(req, res);
	}

	function viewDir(req, res) {
		// read dir
		fs.readdir(req.query.path, function(err, files) {
			if (err) {
				res.sendStatus(500);
				return;
			}
			res.locals.files = files;
			userHandler.dir(req, res);
		});
	}

	function file(callback) {
		userHandler.file = callback;
	}

	function dir() {
		userHandler.dir = callback;
	}

	function listen() {
		app.listen.apply(app, arguments);
	}

	return {
		file: file,
		dir: dir,
		listen: listen
	};
}

module.export = createWebViewer;
<|endoftext|>


<commit_before>const storage = require('../storage');
const mozlog = require('../log');
const log = mozlog('send.download');

module.exports = async function(req, res) {
  const id = req.params.id;
  try {
    const meta = req.meta;
    const contentLength = await storage.length(id);
    res.writeHead(200, {
      'Content-Disposition': 'attachment',
      'Content-Type': 'application/octet-stream',
      'Content-Length': contentLength,
      'WWW-Authenticate': `send-v1 ${req.nonce}`
    });
    const file_stream = storage.get(id);

    file_stream.on('end', async () => {
      const dl = meta.dl + 1;
      const dlimit = meta.dlimit;
      try {
        if (dl >= dlimit) {
          await storage.del(id);
        } else {
          await storage.setField(id, 'dl', dl);
        }
      } catch (e) {
        log.info('StorageError:', id);
      }
    });
    file_stream.pipe(res);
  } catch (e) {
    res.sendStatus(404);
  }
};
<commit_msg>track bytes sent on download<commit_after>const storage = require('../storage');
const mozlog = require('../log');
const log = mozlog('send.download');

module.exports = async function(req, res) {
  const id = req.params.id;
  try {
    const meta = req.meta;
    const contentLength = await storage.length(id);
    res.writeHead(200, {
      'Content-Disposition': 'attachment',
      'Content-Type': 'application/octet-stream',
      'Content-Length': contentLength,
      'WWW-Authenticate': `send-v1 ${req.nonce}`
    });
    const file_stream = storage.get(id);
    let sentBytes = 0;
    file_stream.on('data', c => (sentBytes += c.length));
    file_stream.on('end', async () => {
      if (sentBytes < contentLength) {
        return;
      }
      const dl = meta.dl + 1;
      const dlimit = meta.dlimit;
      try {
        if (dl >= dlimit) {
          await storage.del(id);
        } else {
          await storage.setField(id, 'dl', dl);
        }
      } catch (e) {
        log.info('StorageError:', id);
      }
    });
    file_stream.pipe(res);
  } catch (e) {
    res.sendStatus(404);
  }
};
<|endoftext|>


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

import TextArea from 'src/TextArea';
import Widget from 'src/Widget';
import WidgetHeader from 'src/WidgetHeader';

import PropTypeDescTable from 'components/PropTypeDescTable';
import doc from 'assets/propTypes/TextArea.json';

class TextAreaExamples extends Component {

    constructor(props) {

        super(props);

        this.state = {
            value: {}
        };

        this.changeHandler = ::this.changeHandler;

    }

    changeHandler(v, index) {
        const {value} = this.state;
        value[index] = v;
        this.setState({
            value
        });
    }

    render() {

        const {value} = this.state;

        return (

TextArea

                    A TextArea is an interface for users to input a value in a range. TextArea can be
                    continuous or discrete.

Examples

                <Widget>
                    <WidgetHeader className="example-header" title="TextArea Example"/>

TextArea with auto size.

                            <TextArea placeholder="placeholder"
                                      autoHeight={true}
                                      value={value[0]}
                                      onChange={v => this.changeHandler(v, 0)}/>

                </Widget>

                <Widget>
                    <WidgetHeader className="example-header" title="TextArea Example"/>

TextArea with regular size.

                            <TextArea placeholder="placeholder"
                                      autoHeight={false}
                                      wordCountVisible={true}
                                      maxLength={100}
                                      value={value[1]}
                                      onChange={v => this.changeHandler(v, 1)}/>

                </Widget>

Properties

                <PropTypeDescTable data={doc}/>

        );
    }
}

export default TextAreaExamples;<commit_msg>update TextAreaExamples<commit_after>import React, {Component} from 'react';

import TextArea from 'src/TextArea';
import Widget from 'src/Widget';
import WidgetHeader from 'src/WidgetHeader';

import PropTypeDescTable from 'components/PropTypeDescTable';
import doc from 'assets/propTypes/TextArea.json';

class TextAreaExamples extends Component {

    constructor(props) {

        super(props);

        this.state = {
            value: {}
        };

    }

    changeHandler = (v, index) => {
        const {value} = this.state;
        value[index] = v;
        this.setState({
            value
        });
    };

    render() {

        const {value} = this.state;

        return (

TextArea

                    A TextArea is an interface for users to input a value in a range. TextArea can be
                    continuous or discrete.

Examples

                <Widget>
                    <WidgetHeader className="example-header" title="TextArea Example"/>

TextArea with auto size.

                            <TextArea placeholder="placeholder"
                                      autoHeight={true}
                                      value={value[0]}
                                      onChange={v => this.changeHandler(v, 0)}/>

                </Widget>

                <Widget>
                    <WidgetHeader className="example-header" title="TextArea Example"/>

TextArea with regular size.

                            <TextArea placeholder="placeholder"
                                      autoHeight={false}
                                      wordCountVisible={true}
                                      maxLength={100}
                                      value={value[1]}
                                      onChange={v => this.changeHandler(v, 1)}/>

                </Widget>

Properties

                <PropTypeDescTable data={doc}/>

        );
    }
}

export default TextAreaExamples;<|endoftext|>


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

import TimeLine from 'src/TimeLine/index';
import Widget from 'src/Widget/index';
import WidgetHeader from 'src/WidgetHeader/index';

import PropTypeDescTable from 'components/PropTypeDescTable';
import doc from 'examples/assets/propTypes/TimeLine.json';

export default class TimeLineExamples extends Component {

    constructor(props) {
        super(props);
    }

    render() {
        let data = [{
            type: 'header',
            headerText: 'Now'
        }, {
            theme: TimeLine.Theme.PRIMARY,
            type: 'title',
            contentText: 'just a title.',
            hasBorder: false,
            date: '2017-05-03 15:16:34'
        }, {
            theme: TimeLine.Theme.HIGHLIGHT,
            type: 'title',
            hasBorder: true,
            contentText: 'just a title.',
            date: '2017-05-03 15:16:34'
        }, {
            theme: TimeLine.Theme.SUCCESS,
            type: 'title',
            contentText: 'just a title.',
            hasBorder: true,
            date: '2017-05-03 15:16:34'
        }, {
            theme: TimeLine.Theme.WARNING,
            type: 'title',
            hasBorder: true,
            contentText: 'example content.',
            date: '2017-05-06 12:52:12'
        }, {
            theme: TimeLine.Theme.ERROR,
            type: 'title',
            hasBorder: true,
            contentText: 'welcome to China.',
            date: '2017-05-03 15:16:34'
        }, {
            type: 'header',
            headerText: '2016'
        }];

        return (

TimeLine

                    The TimeLine is used to display time flow information vertically.

Examples

                <Widget>

                    <WidgetHeader className="example-header" title="TimeLine Example"/>

A simple TimeLine example.

                            <TimeLine data={data}/>

                </Widget>

Properties

                <PropTypeDescTable data={doc}/>

        );
    }
}<commit_msg>update TimeLineExamples<commit_after>import React, {Component} from 'react';

import TimeLine from 'src/TimeLine/index';
import Widget from 'src/Widget/index';
import WidgetHeader from 'src/WidgetHeader/index';

import PropTypeDescTable from 'components/PropTypeDescTable';
import doc from 'examples/assets/propTypes/TimeLine.json';

class TimeLineExamples extends Component {

    constructor(props) {

        super(props);

        this.data = [{
            type: 'header',
            headerText: 'Now'
        }, {
            theme: TimeLine.Theme.PRIMARY,
            type: 'title',
            contentText: 'just a title.',
            hasBorder: false,
            date: '2017-05-03 15:16:34'
        }, {
            theme: TimeLine.Theme.HIGHLIGHT,
            type: 'title',
            hasBorder: true,
            contentText: 'just a title.',
            date: '2017-05-03 15:16:34'
        }, {
            theme: TimeLine.Theme.SUCCESS,
            type: 'title',
            contentText: 'just a title.',
            hasBorder: true,
            date: '2017-05-03 15:16:34'
        }, {
            theme: TimeLine.Theme.WARNING,
            type: 'title',
            hasBorder: true,
            contentText: 'example content.',
            date: '2017-05-06 12:52:12'
        }, {
            theme: TimeLine.Theme.ERROR,
            type: 'title',
            hasBorder: true,
            contentText: 'welcome to China.',
            date: '2017-05-03 15:16:34'
        }, {
            type: 'header',
            headerText: '2016'
        }];

    }

    render() {
        return (

TimeLine

                    The TimeLine is used to display time flow information vertically.

Examples

                <Widget>

                    <WidgetHeader className="example-header" title="TimeLine Example"/>

A simple TimeLine example.

                            <TimeLine data={this.data}/>

                </Widget>

Properties

                <PropTypeDescTable data={doc}/>

        );
    }
}

export default TimeLineExamples;<|endoftext|>


<commit_before>let React = require("react"),
  {AbstractShot} = require("../../../addon/dist/lib/shot.js");

exports.Frame = React.createClass({
  render: function () {
    if (this.props.data === null) {
      return
Not Found
;
    }

    console.log("props.id", this.props.id);
    console.log("props", Object.getOwnPropertyNames(this.props.shot));

    let shot = new AbstractShot("http://localhost:10080/", this.props.id, this.props.shot);

    let favicon = "";
    if (shot.favicon) {
      favicon = <link rel="shortcut icon" href={shot.favicon} />;
    }

    let snippet = "",
      clipNames = shot.clipNames();

    if (clipNames.length) {
      snippet = shot.getClip(clipNames[0]).image.url;
    }

    console.log("shot names", Object.getOwnPropertyNames(shot));
    console.log("clipNames", clipNames);

    let linkTextShort = "";
    if (shot.url) {
      let txt = shot.url;
      if (txt.length > 50) {
        txt = txt.slice(0, 50) + '...';
      }
      linkTextShort = txt;
    }

    return


<commit_before>var INITIAL_QUERY = '@2days';

function favicon(url) {
    return URI(url).path('favicon.ico').search('')
        .toString().replace(/\?$/, '');
}

function entryFill(entry) {
    entry.favicon = favicon(entry.link);
    var date = new Date(entry.date);
    entry.dateString = [
        1900 + date.getYear(),
        1 + date.getMonth(),
        date.getDate()
    ].join('-');
    entry.classes = entry.tags.map(function(tag) {
        return 'tag-' + tag;
    }).join(' ');
}

function SearchCtrl($scope, $http) {
    $scope.query = INITIAL_QUERY;
    $scope.busy = false;
    $scope.dirty = true;

    $scope.update = function() {
        if (!$scope.busy) {
            $scope.busy = true;
            $scope.dirty = false;
            $http.get(URI('/elfeed/search').search({
                q: $scope.query
            }).toString()).success(function(data) {
                data.forEach(entryFill);
                $scope.entries = data;
                $scope.busy = false;
                if ($scope.dirty) $scope.update();
            });
        } else {
            $scope.dirty = true;
        }
    };

    $scope.time = 0;
    function poll() {
        $http.get(URI('/elfeed/update').search({
            time: $scope.time
        }).toString()).success(function(data) {
            $scope.time = data;
            $scope.update();
            poll();
        });
    }

    poll();
}
<commit_msg>Unblur on submit in elfeed web.<commit_after>var INITIAL_QUERY = '@2days';

function favicon(url) {
    return URI(url).path('favicon.ico').search('')
        .toString().replace(/\?$/, '');
}

function entryFill(entry) {
    entry.favicon = favicon(entry.link);
    var date = new Date(entry.date);
    entry.dateString = [
        1900 + date.getYear(),
        1 + date.getMonth(),
        date.getDate()
    ].join('-');
    entry.classes = entry.tags.map(function(tag) {
        return 'tag-' + tag;
    }).join(' ');
}

function SearchCtrl($scope, $http) {
    $scope.query = INITIAL_QUERY;
    $scope.busy = false;
    $scope.dirty = true;

    $scope.update = function(blur) {
        if (!$scope.busy) {
            $scope.busy = true;
            $scope.dirty = false;
            $http.get(URI('/elfeed/search').search({
                q: $scope.query
            }).toString()).success(function(data) {
                data.forEach(entryFill);
                $scope.entries = data;
                $scope.busy = false;
                if ($scope.dirty) $scope.update();
            });
        } else {
            $scope.dirty = true;
        }

        if (blur) {
            // Is there a "right" way to do this? I don't think there is.
            document.getElementById('query').blur();
        }
    };

    $scope.time = 0;
    function poll() {
        $http.get(URI('/elfeed/update').search({
            time: $scope.time
        }).toString()).success(function(data) {
            $scope.time = data;
            $scope.update();
            poll();
        });
    }

    poll();
}

function ViewCtrl($scope) {
    $scope.ref = null;
    //$scope.ref = "77a01f799ef6ac4a1aa4bfc218050a95eeb0875a";
}
<|endoftext|>


<commit_before>/**
 * HyperBolts ϟ (https://hyperbolts.io)
 *
 * Copyright © 2015-present Pace IT Systems Ltd.
 * All rights reserved.
 *
 * @author  Pace IT Systems Ltd
 * @license MIT
 */

// Mounted components
const mounted = {};

// Next UUID
let uuid = 0;

/**
 * Return object containing references of
 * components listening to store.
 *
 * @return {object} mounted components
 */
const getListeningComponents = () => mounted;

/**
 * Return array of component source objects.
 *
 * @param  {object} component react component
 * @return {array}            sources
 */
const getSources = component => {
    const props    = component.props || {};
    const location = props.location || {};

    // Return parsed sources
    return parseSources(
        component.getSources(
            require('..').uri, // eslint-disable-line global-require
            props.params,
            location.query
        )
    );
};

/**
 * Return whether the passed source is a full URL.
 *
 * @param  {string} source source
 * @return {bool}          is full URL
 */
const isUrl = source => {
    switch (true) {
        case source.substr(0, 7) === 'http://':
        case source.substr(0, 8) === 'https://':
        case source.substr(0, 2) === '//':
            return true;
        default:
            return false;
    }
};

/**
 * Return parsed sources, converting any dependent
 * sources into real sources or removing them from
 * the array.
 *
 * @param  {array} sources  sources
 * @param  {array} original original sources
 * @return {array}          sources
 */
const parseSources = (sources, original = []) => {
    const Hyper    = require('..'); // eslint-disable-line global-require
    const Store    = Hyper.store;
    let additional = [];

    // If sources is a string or object, wrap in an array
    if (typeof sources === 'string' || sources.source !== undefined) {
        sources = [sources];
    }

    // Filter sources
    sources = sources.filter((config, i) => {
        const data = [];
        let required;

        // Convert string to source
        if (typeof config === 'string') {
            sources[i] = {
                source: config
            };

            return true;
        }

        // If we aren't processing a dependant data source, include
        // in filtered array
        if (config.requires === undefined && config.required === undefined) {
            return true;
        }

        // Make sure requirements are an array
        const requires = [].concat(config.requires || config.required);

        // Attempt to retrieve dependant data
        for (required of requires) {
            const match = sources
                .concat(original)
                .find(source => source.key === required); // eslint-disable-line no-loop-func

            // If we don't have a source, don't include in filtered array
            if (match === undefined) {
                return false;
            }

            // Retrieve cache from store
            const cache = Store.getCachedState(match.source);

            // If we have no valid cache, don't include in filtered array
            if (cache === undefined || cache.loading === true || cache.error === true) {
                return false;
            }

            data.push(cache);
        }

        // Retrieve parsed sources
        const parsed = parseSources(
            (config.sources || config.source)(...data),
            sources
        );

        // Add to parsed sources and remove from
        // filtered array
        additional = additional.concat(parsed);
        return false;
    });

    // If we have no additional sources, just return
    // base array
    if (additional.length === 0) {
        return sources;
    }

    // Make sure all additional sources are loaded
    // then return merged sources
    Store.loadState(additional.map(config => config.source));
    return sources.concat(additional);
};

/**
 * Parse parsed source into a full URL.
 *
 * @param  {string} source source
 * @return {string}        parsed source
 */
const parseUrl = source => {

    // If source is a URI string, add protocol and host
    if (isUrl(source) === false) {
        const parser = document.createElement('a');

        parser.href = location.href;
        source = `${parser.protocol}//${parser.host}/${source}`;
    }

    // If source has no protocol, prefix it to beginning
    else if (source.substr(0, 2) === '//') {
        source = location.protocol + source;
    }

    // Sanitize source
    return sanitizeSource(source);
};

/**
 * Remove reference to mounted component.
 *
 * @param  {int}  id identifier
 * @return {void}
 */
const removeListeningComponent = id => delete mounted[id];

/**
 * Return sanitized source with deduplicated slashes
 * and alphabetical query string so we can match against
 * cache.
 *
 * @param  {string} source source
 * @return {string}        parsed source
 */
const sanitizeSource = source => {
    const matches = ['http://', 'https://', '//'];
    const index   = source.indexOf('?');
    let protocol  = '';
    let query     = '';
    let match;

    // Alphabeticalise query string
    if (index !== -1) {
        query = source.substr(index + 1);
        query = query
            .split('&')
            .sort()
            .join('&');

        source = source.substr(0, index);
    }

    // To deduplicate slashes we first need to work
    // out whether we have a full URL. If we do
    // remove protocol from source
    for (match of matches) {
        if (source.substr(0, match.length) === match) {
            source = source.substr(match.length);
            protocol = match;
            break;
        }
    }

    // Deduplicate and remove remove leading
    // and traling slashes
    source = source
        .replace(/\/+/g, '/')
        .replace(/^\/|\/$/g, '');

    // Return rebuild source
    return decodeURIComponent(protocol + source + query);
};

/**
 * Store component listening to store. We need to know what
 * components are connected to trigger the correct state
 * on transition.
 *
 * @param  {object} component react component
 * @return {int}              uuid
 */
const setListeningComponent = component => {
    mounted[uuid++] = component;
    return uuid - 1;
};

// Export module
module.exports = {
    getListeningComponents,
    getSources,
    isUrl,
    parseSources,
    parseUrl,
    removeListeningComponent,
    sanitizeSource,
    setListeningComponent
};
<commit_msg>Fix bug with string sources<commit_after>/**
 * HyperBolts ϟ (https://hyperbolts.io)
 *
 * Copyright © 2015-present Pace IT Systems Ltd.
 * All rights reserved.
 *
 * @author  Pace IT Systems Ltd
 * @license MIT
 */

// Mounted components
const mounted = {};

// Next UUID
let uuid = 0;

/**
 * Return object containing references of
 * components listening to store.
 *
 * @return {object} mounted components
 */
const getListeningComponents = () => mounted;

/**
 * Return array of component source objects.
 *
 * @param  {object} component react component
 * @return {array}            sources
 */
const getSources = component => {
    const props    = component.props || {};
    const location = props.location || {};

    // Return parsed sources
    return parseSources(
        component.getSources(
            require('..').uri, // eslint-disable-line global-require
            props.params,
            location.query
        )
    );
};

/**
 * Return whether the passed source is a full URL.
 *
 * @param  {string} source source
 * @return {bool}          is full URL
 */
const isUrl = source => {
    switch (true) {
        case source.substr(0, 7) === 'http://':
        case source.substr(0, 8) === 'https://':
        case source.substr(0, 2) === '//':
            return true;
        default:
            return false;
    }
};

/**
 * Return parsed sources, converting any dependent
 * sources into real sources or removing them from
 * the array.
 *
 * @param  {array} sources  sources
 * @param  {array} original original sources
 * @return {array}          sources
 */
const parseSources = (sources, original = []) => {
    const Hyper    = require('..'); // eslint-disable-line global-require
    const Store    = Hyper.store;
    let additional = [];

    // If sources is a string or object, wrap in an array
    if (typeof sources === 'string' || sources.source !== undefined) {
        sources = [sources];
    }

    // Make sure all array values are source objects
    sources = sources.map(config => {
        if (typeof config === 'string') {
            return {
                source: config
            };
        }

        return config;
    });

    // Filter sources
    sources = sources.filter(config => {
        const data = [];
        let required;

        // If we aren't processing a dependant data source, include
        // in filtered array
        if (config.requires === undefined && config.required === undefined) {
            return true;
        }

        // Make sure requirements are an array
        const requires = [].concat(config.requires || config.required);

        // Attempt to retrieve dependant data
        for (required of requires) {
            const match = sources
                .concat(original)
                .find(source => source.key === required); // eslint-disable-line no-loop-func

            // If we don't have a source, don't include in filtered array
            if (match === undefined) {
                return false;
            }

            // Retrieve cache from store
            const cache = Store.getCachedState(match.source);

            // If we have no valid cache, don't include in filtered array
            if (cache === undefined || cache.loading === true || cache.error === true) {
                return false;
            }

            data.push(cache);
        }

        // Retrieve parsed sources
        const parsed = parseSources(
            (config.sources || config.source)(...data),
            sources
        );

        // Add to parsed sources and remove from
        // filtered array
        additional = additional.concat(parsed);
        return false;
    });

    // If we have no additional sources, just return
    // base array
    if (additional.length === 0) {
        return sources;
    }

    // Make sure all additional sources are loaded
    // then return merged sources
    Store.loadState(additional.map(config => config.source));
    return sources.concat(additional);
};

/**
 * Parse parsed source into a full URL.
 *
 * @param  {string} source source
 * @return {string}        parsed source
 */
const parseUrl = source => {

    // If source is a URI string, add protocol and host
    if (isUrl(source) === false) {
        const parser = document.createElement('a');

        parser.href = location.href;
        source = `${parser.protocol}//${parser.host}/${source}`;
    }

    // If source has no protocol, prefix it to beginning
    else if (source.substr(0, 2) === '//') {
        source = location.protocol + source;
    }

    // Sanitize source
    return sanitizeSource(source);
};

/**
 * Remove reference to mounted component.
 *
 * @param  {int}  id identifier
 * @return {void}
 */
const removeListeningComponent = id => delete mounted[id];

/**
 * Return sanitized source with deduplicated slashes
 * and alphabetical query string so we can match against
 * cache.
 *
 * @param  {string} source source
 * @return {string}        parsed source
 */
const sanitizeSource = source => {
    const matches = ['http://', 'https://', '//'];
    const index   = source.indexOf('?');
    let protocol  = '';
    let query     = '';
    let match;

    // Alphabeticalise query string
    if (index !== -1) {
        query = source.substr(index + 1);
        query = query
            .split('&')
            .sort()
            .join('&');

        source = source.substr(0, index);
    }

    // To deduplicate slashes we first need to work
    // out whether we have a full URL. If we do
    // remove protocol from source
    for (match of matches) {
        if (source.substr(0, match.length) === match) {
            source = source.substr(match.length);
            protocol = match;
            break;
        }
    }

    // Deduplicate and remove remove leading
    // and traling slashes
    source = source
        .replace(/\/+/g, '/')
        .replace(/^\/|\/$/g, '');

    // Return rebuild source
    return decodeURIComponent(protocol + source + query);
};

/**
 * Store component listening to store. We need to know what
 * components are connected to trigger the correct state
 * on transition.
 *
 * @param  {object} component react component
 * @return {int}              uuid
 */
const setListeningComponent = component => {
    mounted[uuid++] = component;
    return uuid - 1;
};

// Export module
module.exports = {
    getListeningComponents,
    getSources,
    isUrl,
    parseSources,
    parseUrl,
    removeListeningComponent,
    sanitizeSource,
    setListeningComponent
};
<|endoftext|>