<commit_before>jQuery(function() {

	var dataSrc = $('#cluster-data'),
	       prnt = dataSrc.parent(),
	       info = $('

'),
	      nodes = dataSrc.data('seed'),
	   coreTool = dataSrc.data('tool');

	var threshold = $('<select></select>');
	for (var idx = 0; idx <= 100; idx += 5) {
		threshold.append($('<option value=' + idx + '>' + idx + '%</option>'));
	}

	var render = function(nodes) {
		var    margin = { 'top': 0, 'right': 0, 'bottom': 0, 'left': 0 },
		           ex = $('.cluster'),
		     cellDims = { 'x': 6, 'y': 6 },
		          min = Infinity,
		          max = -Infinity,
		   clusterMin = {},
		   clusterIds = {}
		         days = [],
		       dayMap = {},
		      userMap = {},
		      toolMap = {},
		lastInCluster = {},
		        tools = [],
		       matrix = [],
			 clusterId = 0,
		       ratios = {}
			;
		ex.remove();

		nodes.forEach(function(node) {
			if (!ratios[node.cluster]) {
				ratios[node.cluster] = {
					'hit': 0,
					'total': 0
				};
			}
			if (node.tool == coreTool) {
				++ratios[node.cluster].hit;
			}
			++ratios[node.cluster].total;
		});
		var th = threshold.val()/100;
		nodes = nodes.filter(function(node) {
			return ratios[node.cluster].hit/ratios[node.cluster].total >= th;
		});

		console.log(nodes);
		nodes.forEach(function(node) {
			// track min and max dates so we know what range to generate for columns
			node.first_use = new Date(node.first_use);
			min = Math.min(min, node.first_use);
			max = Math.max(max, node.first_use);
			// secondary sorting criterion, after cluster size, cluster start date
			if (clusterMin[node.cluster] === undefined) {
				clusterIds[node.cluster] = ++clusterId;
				clusterMin[node.cluster] = Infinity;
			}
			clusterMin[node.cluster] = Math.min(clusterMin[node.cluster], node.first_use);
			// map of tools used to generate legend
			if (toolMap[node.tool] === undefined) {
				toolMap[node.tool] = 1;
				tools.push(node.tool);
			}
		});
		nodes.sort(function(a, b) {
			// in the same cluster, users are ordered by the first time they used a tool
			if (a.cluster == b.cluster) {
				return d3.ascending(a.first_use, b.first_use);
			}
			// between clusters, first we want clusters with the most users
			if (a.size != b.size) {
				return d3.descending(1*a.size, 1*b.size);
			}
			// in the event of a tie, go with the one that had the first usage
			if (clusterMin[a.cluster] < clusterMin[b.cluster]) {
				return -1;
			}
			if (clusterMin[a.cluster] > clusterMin[b.cluster]) {
				return 1;
			}
			// if that's still a tie, just order by the arbitrary cluster name
			return d3.ascending(a.cluster, b.cluster);
		});

		// move the tool represented by this page to the front of the legend so it's always assigned the same color
		tools.sort(function(a, b) {
			if (a == coreTool) {
				return -1;
			}
			if (b == coreTool) {
				return 1;
			}
			return d3.ascending(a, b);
		});
		tools.forEach(function(tool, idx) {
			toolMap[tool] = idx;
		});

		// generate map of day => column index
		for (var dt = new Date(min); dt <= new Date(max); dt.setDate(dt.getDate() + 1)) {
			var name = dt.toLocaleDateString();
			dayMap[name] = days.length;
			days.push(name);
		}

		var used = {};
		nodes.forEach(function(node, i) {
			// generate row for this user
			if (userMap[node.uid] === undefined) {
				userMap[node.uid] = matrix.length;
				matrix.push([]);
			}
			// determine whether node is the last in the cluster, in which case we can style its row a little differently to break them up
			if (lastInCluster[clusterIds[node.cluster]] === undefined) {
				lastInCluster[clusterIds[node.cluster]] = Infinity;
			}
			lastInCluster[clusterIds[node.cluster]] = Math.min(lastInCluster[clusterIds[node.cluster]], userMap[node.uid]);

			var      x = dayMap[node.first_use.toLocaleDateString()],
				      y = userMap[node.uid]
			shouldDraw = !used[x+':'+y]
				 ;
			if (shouldDraw) {
				used[x+':'+y] = [node.tool];
			}
			else {
				used[x+':'+y].push(node.tool);
				return;
			}

			matrix[userMap[node.uid]].push({
				'x': x,
				'y': y,
				'toolId': toolMap[node.tool],
				'toolName': node.tool,
				'clusterId': clusterIds[node.cluster],
				'clusterName': node.cluster,
				'size': node.size,
				'uid': node.uid,
				'date': node.first_use.toLocaleDateString(),
				'lastInCluster': lastInCluster[node.uid]
			})
		});
		// dimension and color setup
		var  width = cellDims.x * days.length,
		    height = cellDims.y * matrix.length,
		         x = d3.scale.ordinal().rangeBands([0, width]).domain(d3.range(days.length)),
		         y = d3.scale.ordinal().rangeBands([0, height]).domain(d3.range(matrix.length)),
		         c = d3.scale.category20().domain(d3.range(tools.length)),
		toolLegend = $('

');

		// draw tool color legend
		tools.forEach(function(tool, idx) {
			toolLegend.append($('

').css('backgroundColor', c(idx)).text(tool));
		});
		prnt.append(toolLegend);

		// background
		var svg = d3.select(dataSrc.parent()[0]).append("svg")
			.attr('class', 'cluster')
			.attr("width", width + margin.left + margin.right)
			.attr("height", height + margin.top + margin.bottom)
			.style("margin-left", -margin.left + "px")
			.append("g")
				.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
		svg.append("rect")
			.attr("class", "background")
			.attr("width", width)
			.attr("height", height);

		// column lines
		var column = svg.selectAll(".column")
			.data(days)
				.enter().append("g")
				.attr("class", "column")
				.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
		column.append("line")
			.attr("x1", -height);

		// callbacks to highlight clusters on hover
		var isIn = false,
		mouseover = function(d) {
			// NB: addClass doesn't work on svg elements without including some more libraries
			$('.cluster-' + d.clusterId).attr('class', 'cell cluster-' + d.clusterId + ' highlight');
			info
				.css('left', mouse.x)
				.css('top', mouse.y)
				.empty()
				.append($('

').text(d.clusterName.replace(/[|]/g, ', ').replace(', UNKNOWN', '')))
				.append($('
Date: ' + d.date + '
'))
				.append($('
Tool: ' + used[d.x+':'+d.y].join(', ') + '
'))
				.show('fast')
				;
			isIn = true;
		},
		mouseout = function(d) {
			$('.cell').each(function(_, cell) {
				cell = $(cell);
				cell.attr('class', cell.attr('class').replace(' highlight', ''));
			});
			isIn = false;
			setTimeout(function() {
				if (!isIn) {
					info.hide('slow');
				}
			}, 500);
		};

		// rows
		var row = svg.selectAll(".row")
			.data(matrix)
			.enter().append("g")
				.attr("class", function(r) { return 'row cluster-' + r[0].clusterId; })
				.attr("transform", function(d, i) { return "translate(0," + y(i) + ")"; })
				.each(function row(row) {
					// cells
					var cell = d3.select(this).selectAll(".cell")
						.data(row)
							.enter().append("rect")
								.attr("class", function(d) { return 'cell cluster-' + d.clusterId; })
								.attr("x", function(d) { return x(d.x); })
								.attr('rx', 2)
								.attr('ry', 2)
								.attr("width", cellDims.x)
								.attr("height", cellDims.y)
								.style("fill", function(d) { return c(d.toolId); })
								.on('mouseover', mouseover)
								.on('mouseout', mouseout);
				});
		row.append("line")
			.attr("x2", width)
			.attr('class', function(x) {
				return x[0].y == lastInCluster[x[0].clusterId] ? 'last' : '';
			});

		// move info box around to an appropriate location for hover
		var svgPrnt = $('svg.cluster').parent(), mouse = {'x': 0, 'y': 0};
		svgPrnt.mousemove(function(evt) {
			var offset = svgPrnt.offset();
			mouse.x = evt.pageX - offset.left + 'px';
			mouse.y = evt.pageY - offset.top + 'px';
		});
		$('.cluster').show('fast');
	};

	if (!nodes) {
		return;
	}
	$('#no-usage').remove();

	// do groupings by year and semester, corresponding to selector buttons
	var byYear = {}, lastYear = null;
	nodes.forEach(function(semester) {
		semester.forEach(function(node) {
			if (byYear[node.year] === undefined) {
				byYear[node.year] = {};
			}
			var semesterName = node.cluster.match(/^(.*?)\d{4}/)[1];
			if (byYear[node.year][semesterName] === undefined) {
				byYear[node.year][semesterName] = [];
			}
			byYear[node.year][semesterName].push(node);
			lastYear = node.year;
		});
	});
	// add buttons
	var   yearList = $('

'),
	  semesterList = $('

'),
	getCurrentYear = function() {
		return $('#cluster-years').children('.selected').text();
	},
	getCurrentSemester = function() {
		return $('#semester-list').children('.selected').text();
	};
	var ma = document.cookie.toString().match(/classroomthreshold=(\d+)/);
	if (ma) {
		threshold.val(ma[1]);
	}
	else {
		threshold.val(5);
	}
	prnt
		.append(yearList)
		.append(semesterList)
		.append($('
Show clusters with at least
').append(threshold).append(document.createTextNode(' usage of this tool')))
		;
	for (var year in byYear) {
		var yearLi = $('
/li>').text(year);
		if (year == lastYear) {
			yearLi.addClass('selected');
		}
		yearList.append(yearLi);
	}

	// callbacks to change viewed cluster set
	yearList.click(function(evt) {
		$('.cluster-time-selector.year li').removeClass('selected');
		$(evt.target).addClass('selected');
		listSemesters();
		draw();
	});
	semesterList.click(function(evt) {
		$('.cluster-time-selector.semester li').removeClass('selected');
		$(evt.target).addClass('selected');
		draw();
	});
	// show the appropriate semesters for a given year, since we might not always have all of them
	var listSemesters = function() {
		var lastSemester = null;
		semesterList.empty();
		for (var semester in byYear[getCurrentYear()]) {
			var sem = $('

').text(semester)
			if (!lastSemester) {
				lastSemester = sem;
			}
			semesterList.prepend(sem);
		}
		lastSemester.addClass('selected');
	};
	// refresh view
	var draw = function() {
		throbber.css('visibility', 'visible');
		setTimeout(function() {
			render(byYear[getCurrentYear()][getCurrentSemester()]);
			throbber.css('visibility', 'hidden');
		}, 50);
	};
	dataSrc.parent().append(info);
	listSemesters();
	var throbber = $('').insertAfter(semesterList);
	threshold.change(function(evt) {
		document.cookie = 'classroomthreshold=' + threshold.val();
		draw();
	});
	draw();
});
<commit_msg>[NANOHUB] expand labeling<commit_after>jQuery(function() {

	var dataSrc = $('#cluster-data'),
	       prnt = dataSrc.parent(),
	       info = $('

'),
	      nodes = dataSrc.data('seed'),
	   coreTool = dataSrc.data('tool');

	var threshold = $('<select></select>');
	for (var idx = 0; idx <= 100; idx += 5) {
		threshold.append($('<option value=' + idx + '>' + idx + '%</option>'));
	}

	var render = function(nodes) {
		var    margin = { 'top': 0, 'right': 0, 'bottom': 0, 'left': 0 },
		           ex = $('.cluster'),
		     cellDims = { 'x': 6, 'y': 6 },
		          min = Infinity,
		          max = -Infinity,
		   clusterMin = {},
		   clusterIds = {}
		         days = [],
		       dayMap = {},
		      userMap = {},
		      toolMap = {},
		lastInCluster = {},
		        tools = [],
		       matrix = [],
			 clusterId = 0,
		       ratios = {}
			;
		ex.remove();

		nodes.forEach(function(node) {
			if (!ratios[node.cluster]) {
				ratios[node.cluster] = {
					'hit': 0,
					'total': 0
				};
			}
			if (node.tool == coreTool) {
				++ratios[node.cluster].hit;
			}
			++ratios[node.cluster].total;
		});
		var th = threshold.val()/100;
		nodes = nodes.filter(function(node) {
			return ratios[node.cluster].hit/ratios[node.cluster].total >= th;
		});

		nodes.forEach(function(node) {
			// track min and max dates so we know what range to generate for columns
			node.first_use = new Date(node.first_use);
			min = Math.min(min, node.first_use);
			max = Math.max(max, node.first_use);
			// secondary sorting criterion, after cluster size, cluster start date
			if (clusterMin[node.cluster] === undefined) {
				clusterIds[node.cluster] = ++clusterId;
				clusterMin[node.cluster] = Infinity;
			}
			clusterMin[node.cluster] = Math.min(clusterMin[node.cluster], node.first_use);
			// map of tools used to generate legend
			if (toolMap[node.tool] === undefined) {
				toolMap[node.tool] = 1;
				tools.push(node.tool);
			}
		});
		nodes.sort(function(a, b) {
			// in the same cluster, users are ordered by the first time they used a tool
			if (a.cluster == b.cluster) {
				return d3.ascending(a.first_use, b.first_use);
			}
			// between clusters, first we want clusters with the most users
			if (a.size != b.size) {
				return d3.descending(1*a.size, 1*b.size);
			}
			// in the event of a tie, go with the one that had the first usage
			if (clusterMin[a.cluster] < clusterMin[b.cluster]) {
				return -1;
			}
			if (clusterMin[a.cluster] > clusterMin[b.cluster]) {
				return 1;
			}
			// if that's still a tie, just order by the arbitrary cluster name
			return d3.ascending(a.cluster, b.cluster);
		});

		// move the tool represented by this page to the front of the legend so it's always assigned the same color
		tools.sort(function(a, b) {
			if (a == coreTool) {
				return -1;
			}
			if (b == coreTool) {
				return 1;
			}
			return d3.ascending(a, b);
		});
		tools.forEach(function(tool, idx) {
			toolMap[tool] = idx;
		});

		// generate map of day => column index
		for (var dt = new Date(min); dt <= new Date(max); dt.setDate(dt.getDate() + 1)) {
			var name = dt.toLocaleDateString();
			dayMap[name] = days.length;
			days.push(name);
		}

		var used = {};
		nodes.forEach(function(node, i) {
			// generate row for this user
			if (userMap[node.uid] === undefined) {
				userMap[node.uid] = matrix.length;
				matrix.push([]);
			}
			// determine whether node is the last in the cluster, in which case we can style its row a little differently to break them up
			if (lastInCluster[clusterIds[node.cluster]] === undefined) {
				lastInCluster[clusterIds[node.cluster]] = Infinity;
			}
			lastInCluster[clusterIds[node.cluster]] = Math.min(lastInCluster[clusterIds[node.cluster]], userMap[node.uid]);

			var      x = dayMap[node.first_use.toLocaleDateString()],
				      y = userMap[node.uid]
			shouldDraw = !used[x+':'+y]
				 ;
			if (shouldDraw) {
				used[x+':'+y] = [node.tool];
			}
			else {
				used[x+':'+y].push(node.tool);
				return;
			}

			matrix[userMap[node.uid]].push({
				'x': x,
				'y': y,
				'toolId': toolMap[node.tool],
				'toolName': node.tool,
				'clusterId': clusterIds[node.cluster],
				'clusterName': node.cluster,
				'size': node.size,
				'uid': node.uid,
				'date': node.first_use.toLocaleDateString(),
				'lastInCluster': lastInCluster[node.uid]
			})
		});
		// dimension and color setup
		var  width = cellDims.x * days.length,
		    height = cellDims.y * matrix.length,
		         x = d3.scale.ordinal().rangeBands([0, width]).domain(d3.range(days.length)),
		         y = d3.scale.ordinal().rangeBands([0, height]).domain(d3.range(matrix.length)),
		         c = d3.scale.category20().domain(d3.range(tools.length)),
		toolLegend = $('

');

		// draw tool color legend
		tools.forEach(function(tool, idx) {
			toolLegend.append($('

').css('backgroundColor', c(idx)).text(tool));
		});
		prnt.append(toolLegend);

		// background
		var svg = d3.select(dataSrc.parent()[0]).append("svg")
			.attr('class', 'cluster')
			.attr("width", width + margin.left + margin.right)
			.attr("height", height + margin.top + margin.bottom)
			.style("margin-left", -margin.left + "px")
			.append("g")
				.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
		svg.append("rect")
			.attr("class", "background")
			.attr("width", width)
			.attr("height", height);

		// column lines
		var column = svg.selectAll(".column")
			.data(days)
				.enter().append("g")
				.attr("class", "column")
				.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
		column.append("line")
			.attr("x1", -height);

		// callbacks to highlight clusters on hover
		var isIn = false,
		mouseover = function(d) {
			// NB: addClass doesn't work on svg elements without including some more libraries
			$('.cluster-' + d.clusterId).attr('class', 'cell cluster-' + d.clusterId + ' highlight');
			info
				.css('left', mouse.x)
				.css('top', mouse.y)
				.empty()
				.append($('

').text(d.clusterName.replace(/[|]/g, ', ').replace(', UNKNOWN', '')))
				.append($('
Date: ' + d.date + '
'))
				.append($('
Tool: ' + used[d.x+':'+d.y].join(', ') + '
'))
				.show('fast')
				;
			isIn = true;
		},
		mouseout = function(d) {
			$('.cell').each(function(_, cell) {
				cell = $(cell);
				cell.attr('class', cell.attr('class').replace(' highlight', ''));
			});
			isIn = false;
			setTimeout(function() {
				if (!isIn) {
					info.hide('slow');
				}
			}, 500);
		};

		// rows
		var row = svg.selectAll(".row")
			.data(matrix)
			.enter().append("g")
				.attr("class", function(r) { return 'row cluster-' + r[0].clusterId; })
				.attr("transform", function(d, i) { return "translate(0," + y(i) + ")"; })
				.each(function row(row) {
					// cells
					var cell = d3.select(this).selectAll(".cell")
						.data(row)
							.enter().append("rect")
								.attr("class", function(d) { return 'cell cluster-' + d.clusterId; })
								.attr("x", function(d) { return x(d.x); })
								.attr('rx', 2)
								.attr('ry', 2)
								.attr("width", cellDims.x)
								.attr("height", cellDims.y)
								.style("fill", function(d) { return c(d.toolId); })
								.on('mouseover', mouseover)
								.on('mouseout', mouseout);
				});
		row.append("line")
			.attr("x2", width)
			.attr('class', function(x) {
				return x[0].y == lastInCluster[x[0].clusterId] ? 'last' : '';
			});

		// move info box around to an appropriate location for hover
		var svgPrnt = $('svg.cluster').parent(), mouse = {'x': 0, 'y': 0};
		svgPrnt.mousemove(function(evt) {
			var offset = svgPrnt.offset();
			mouse.x = evt.pageX - offset.left + 'px';
			mouse.y = evt.pageY - offset.top + 'px';
		});
		$('.cluster').show('fast');
	};

	if (!nodes) {
		return;
	}
	$('#no-usage').remove();

	// do groupings by year and semester, corresponding to selector buttons
	var byYear = {}, lastYear = null;
	nodes.forEach(function(semester) {
		semester.forEach(function(node) {
			if (byYear[node.year] === undefined) {
				byYear[node.year] = {};
			}
			var semesterName = node.cluster.match(/^(.*?)\d{4}/)[1];
			if (byYear[node.year][semesterName] === undefined) {
				byYear[node.year][semesterName] = [];
			}
			byYear[node.year][semesterName].push(node);
			lastYear = node.year;
		});
	});
	// add buttons
	var   yearList = $('

'),
	  semesterList = $('

'),
	getCurrentYear = function() {
		return $('#cluster-years').children('.selected').text();
	},
	getCurrentSemester = function() {
		return $('#semester-list').children('.selected').text();
	};
	var ma = document.cookie.toString().match(/classroomthreshold=(\d+)/);
	if (ma) {
		threshold.val(ma[1]);
	}
	else {
		threshold.val(5);
	}
	prnt
		.append(yearList)
		.append(semesterList)
		.append($('
Showing classroom tool usage comproside of at least
').append(threshold).append(document.createTextNode(' sessions with this tool')))
		;
	for (var year in byYear) {
		var yearLi = $('
/li>').text(year);
		if (year == lastYear) {
			yearLi.addClass('selected');
		}
		yearList.append(yearLi);
	}

	// callbacks to change viewed cluster set
	yearList.click(function(evt) {
		$('.cluster-time-selector.year li').removeClass('selected');
		$(evt.target).addClass('selected');
		listSemesters();
		draw();
	});
	semesterList.click(function(evt) {
		$('.cluster-time-selector.semester li').removeClass('selected');
		$(evt.target).addClass('selected');
		draw();
	});
	// show the appropriate semesters for a given year, since we might not always have all of them
	var listSemesters = function() {
		var lastSemester = null;
		semesterList.empty();
		for (var semester in byYear[getCurrentYear()]) {
			var sem = $('

').text(semester)
			if (!lastSemester) {
				lastSemester = sem;
			}
			semesterList.prepend(sem);
		}
		lastSemester.addClass('selected');
	};
	// refresh view
	var draw = function() {
		throbber.css('visibility', 'visible');
		setTimeout(function() {
			render(byYear[getCurrentYear()][getCurrentSemester()]);
			throbber.css('visibility', 'hidden');
		}, 50);
	};
	dataSrc.parent().append(info);
	listSemesters();
	var throbber = $('').insertAfter(semesterList);
	threshold.change(function(evt) {
		document.cookie = 'classroomthreshold=' + threshold.val();
		draw();
	});
	draw();
});
<|endoftext|>


<commit_before>// https://developer.mozilla.org/en/DOM/element.classList
function ClassList(element) {
    this.element = element;
    this.list = {};
    element.className.split(" ").forEach(function (token) { this.list[token] = true; }, this);
}
ClassList.prototype._update = function () {
    this.element.className = Object.keys(this.list).join(" ");
};
ClassList.prototype.add = function (token) {
    this.list[token] = true;
    this._update();
};
ClassList.prototype.remove = function (token) {
    delete this.list[token];
    this._update();
};
ClassList.prototype.contains = function (token) {
    return (token in this.list);
};
ClassList.prototype.toggle = function (token) {
    var contained = this.contains(token);
    this[contained ? "remove" : "add"](token);
    return !contained;
};<commit_msg>remove unused lib file<commit_after><|endoftext|>


<commit_before>let args = {
    frequency:100,					// ( How often the object sends the values - milliseconds )
    gravityNormalized:true,			// ( If the gravity related values to be normalized )
    orientationBase:GyroNorm.GAME,	// ( Can be GyroNorm.GAME or GyroNorm.WORLD. gn.GAME returns orientation values with respect to the head direction of the device. gn.WORLD returns the orientation values with respect to the actual north direction of the world. )
    decimalCount:0,					// ( How many digits after the decimal point will there be in the return values )
    logger:logger,			        // ( Function to be called to log messages from gyronorm.js )
    screenAdjusted:true			    // ( If set to true it will return screen adjusted values. )
};

let gn = new GyroNorm();
const doc = window.document;
const docEl = doc.documentElement;
gn.init(args).then(function(){

    let isAvailable = gn.isAvailable();
    if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
        if(!isAvailable.deviceOrientationAvailable) {
            logger({message:'Device orientation is not available.'});
        }
    }

}).catch(function(e){
    console.log(e);
});

function logger(data) {
    document.getElementById("error-message").innerHTML = data.message + "
";
}
function stop_gn() {
    const cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
    cancelFullScreen.call(doc);
    const forwards = document.getElementById("forwards");
    const backwards = document.getElementById("backwards");
    const left = document.getElementById("left");
    const right = document.getElementById("right");
    gn.stop(gncbt);
    document.getElementById("stop").style.display = "none";
    document.getElementById("go").style.display = "block";
    right.style.fill = "#ffffff";
    left.style.fill = "#ffffff";
    forwards.style.fill = "#ffffff";
    backwards.style.fill = "#ffffff";
}

function start_gn() {
    const requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
    try {
        requestFullScreen.call(docEl);
    } catch (err) {
        console.log(err);
    }
    // let locOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation || screen.orientation.lock;
    //locOrientation('landscape-primary');
    gn.start(gncbt);
    set_head_gn(); //If screenAdjusted is true, it will set the north to the user's direction
    document.getElementById("stop").style.display = "block";
    document.getElementById("go").style.display = "none";
}

//////  SENDING EVENTS
let lastBut = 'h';

function sendIfChanged(b){
   if (b != lastBut){
     button(b);
     lastBut =b;
   }
}

//////  KEY EVENTS
function sendKeyEvents() {
    document.addEventListener("keydown", function (event) {

        let key = 'h';

        switch (event.key) {
            case "ArrowLeft":
                key = 'l';
                break;
            case "ArrowUp":
                key = 'f';
                break;
            case "ArrowRight":
                key = 'r';
                break;
            case "ArrowDown":
                key = 'b';
                break;
            /*default:
                key = 'h';*/
        }
        sendIfChanged(key);
    });
    document.addEventListener("keyup", function (event) {
        sendIfChanged('h')
    });
}

//////  GYRONORM EVENTS
function gncbt(data){
   let wheel = data.dm.gy;
   let drive = data.dm.gz;

   logger({message:""+data.dm.gx+"
"+data.dm.gy+"
"+data.dm.gz});

   let nb = 'h';
    if  (wheel > 2){
        nb = 'r';
    } else if (wheel < -2) {
        nb = 'l';
    } else if (drive > 4) {
        nb = 'b';
    } else if (drive < -4) {
        nb = 'f';
    }
   sendIfChanged(nb);
}

function norm_gn() {
    gn.normalizeGravity(true);
}
function org_gn() {
    gn.normalizeGravity(false);
}
function set_head_gn() {
    gn.setHeadDirection();
}
screen.onorientationchange = function() {
    resizeCamera();
    set_head_gn();
};
window.onload = function() {
    resizeCamera();
    sendKeyEvents();
};
<commit_msg>Camera Resize Test 3<commit_after>let args = {
    frequency:100,					// ( How often the object sends the values - milliseconds )
    gravityNormalized:true,			// ( If the gravity related values to be normalized )
    orientationBase:GyroNorm.GAME,	// ( Can be GyroNorm.GAME or GyroNorm.WORLD. gn.GAME returns orientation values with respect to the head direction of the device. gn.WORLD returns the orientation values with respect to the actual north direction of the world. )
    decimalCount:0,					// ( How many digits after the decimal point will there be in the return values )
    logger:logger,			        // ( Function to be called to log messages from gyronorm.js )
    screenAdjusted:true			    // ( If set to true it will return screen adjusted values. )
};

let gn = new GyroNorm();
const doc = window.document;
const docEl = doc.documentElement;
gn.init(args).then(function(){

    let isAvailable = gn.isAvailable();
    if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) {
        if(!isAvailable.deviceOrientationAvailable) {
            logger({message:'Device orientation is not available.'});
        }
    }

}).catch(function(e){
    console.log(e);
});

function logger(data) {
    document.getElementById("error-message").innerHTML = data.message + "
";
}
function stop_gn() {
    const cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
    cancelFullScreen.call(doc);
    const forwards = document.getElementById("forwards");
    const backwards = document.getElementById("backwards");
    const left = document.getElementById("left");
    const right = document.getElementById("right");
    gn.stop(gncbt);
    document.getElementById("stop").style.display = "none";
    document.getElementById("go").style.display = "block";
    right.style.fill = "#ffffff";
    left.style.fill = "#ffffff";
    forwards.style.fill = "#ffffff";
    backwards.style.fill = "#ffffff";
}

function start_gn() {
    const requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
    try {
        requestFullScreen.call(docEl);
    } catch (err) {
        console.log(err);
    }
    // let locOrientation = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation || screen.orientation.lock;
    //locOrientation('landscape-primary');
    gn.start(gncbt);
    set_head_gn(); //If screenAdjusted is true, it will set the north to the user's direction
    document.getElementById("stop").style.display = "block";
    document.getElementById("go").style.display = "none";
}

//////  SENDING EVENTS
let lastBut = 'h';

function sendIfChanged(b){
   if (b != lastBut){
     button(b);
     lastBut =b;
   }
}

//////  KEY EVENTS
function sendKeyEvents() {
    document.addEventListener("keydown", function (event) {

        let key = 'h';

        switch (event.key) {
            case "ArrowLeft":
                key = 'l';
                break;
            case "ArrowUp":
                key = 'f';
                break;
            case "ArrowRight":
                key = 'r';
                break;
            case "ArrowDown":
                key = 'b';
                break;
            /*default:
                key = 'h';*/
        }
        sendIfChanged(key);
    });
    document.addEventListener("keyup", function (event) {
        sendIfChanged('h')
    });
}

//////  GYRONORM EVENTS
function gncbt(data){
   let wheel = data.dm.gy;
   let drive = data.dm.gz;

   logger({message:""+data.dm.gx+"
"+data.dm.gy+"
"+data.dm.gz});

   let nb = 'h';
    if  (wheel > 2){
        nb = 'r';
    } else if (wheel < -2) {
        nb = 'l';
    } else if (drive > 4) {
        nb = 'b';
    } else if (drive < -4) {
        nb = 'f';
    }
   sendIfChanged(nb);
}

function norm_gn() {
    gn.normalizeGravity(true);
}
function org_gn() {
    gn.normalizeGravity(false);
}
function set_head_gn() {
    gn.setHeadDirection();
}
screen.onorientationchange = function() {
    // resizeCamera();
    set_head_gn();
};
window.onload = function() {
    // resizeCamera();
    sendKeyEvents();
};
<|endoftext|>


<commit_before>var _ = require("lodash");
var async = require("async");
var config = require("config");
var cheerio = require("cheerio");
var querystring = require("querystring");
var dailyRequest = require("request").defaults({
    baseUrl : config.zhihu_daily_api
});

var story = require("../story");
var utils = require("../utils");
const PREFIX = "/api/4/imgs/";
const MINDATE = utils.convertZhihuDateToMoment("20130520");

/**
 * 从知乎日报服务器获取指定日期的知乎日报索引（ID）。
 * @param  {String} p_date       指定的日期。如果小于 20130519，返回值 res 为 {}。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.fetchStoryIndexes = function (p_date, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    // 因知乎日报 API 返回的是指定日期的前一天的日报，
    // 所以要加一天才能获取指定日期的日报。
    var date = utils.nextZhihuDay(p_date);
    if (date)
    {
        if (utils.convertZhihuDateToMoment(date).isBefore(MINDATE))
        {
            // 20130519 之前是没有知乎日报的。
            p_callback(null, {});
        }
        else
        {
            dailyRequest.get({ url: "/news/before/" + date, json: true }, function (err, res, body)
            {
                if (!err && res.statusCode == 200)
                {
                    var indexes = body.stories.map(function (item)
                    {
                        return item.id;
                    });

                    p_callback(null, {
                        date: body.date,
                        indexes: indexes
                    });
                }
                else
                {
                    p_callback(new Error("request zhihu-daily api ('/news/before/:date') error:" + err.message), null);
                }
            });
        }
    }
    else
    {
        p_callback(new Error("p_date has a wrong format."), null);
    }
};

/**
 * 从知乎日报服务器获取指定 Id 的知乎日报。
 * @param  {String} p_id         指定的 Id。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.fetchStory = function (p_id, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    // 首先检查 Id 是否为纯数字。
    if (/^\d+$/.test(p_id))
    {
        dailyRequest.get({ url: "/news/" + p_id, json: true }, function (err, res, body)
        {
            if (!err && res.statusCode == 200)
            {
                var result = {};
                result.id = body.id;
                result.title = body.title;

                // TODO: 图片暂时不入库，后面再说。
                result.image = PREFIX + querystring.escape(body.image);

                result.imageSource = body.image_source;
                result.shareURL = body.share_url;

                if (body.body)
                {
                    var $ = cheerio.load(body.body, { decodeEntities: false });
                    result.backgrounds = $(".headline>.headline-background .headline-background-link").map(function (i, e)
                    {
                        return {
                            href: $(e).attr("href"),
                            title : $(e).children(".heading").text(),
                            text : $(e).children(".heading-content").text()
                        };
                    }).get();

                    result.contents = $(".content-inner>.question").map(function (i, e)
                    {
                        var question = {};
                        question.title = $(e).children(".question-title").text();
                        question.answers = $(e).children(".answer").map(function (i, e)
                        {
                            // TODO: 图片暂时不入库，后面再说。
                            $(e).find(".content img").each(function (i, e)
                            {
                                var src = $(e).attr("src");
                                if (src != null && src != "")
                                {
                                    $(e).attr("src", PREFIX + querystring.escape(src));
                                }
                            });

                            // TODO: 图片暂时不入库，后面再说。
                            var avatar = $(e).find(".meta>.avatar").attr("src");
                            if (avatar != null && avatar != "")
                            {
                                avatar = PREFIX + querystring.escape(avatar);
                            }
                            else
                            {
                                avatar = "";
                            }

                            return {
                                avatar : avatar,
                                name: $(e).find(".meta>.author").text(),
                                bio : $(e).find(".meta>.bio").text(),
                                content : $(e).children(".content").html()
                            };
                        }).get();

                        var a = $(e).find(".view-more>a");
                        if (a.length > 0)
                        {
                            question.link = {
                                href : a.attr("href"),
                                text : a.text(),
                            };
                        }

                        return question;
                    }).get();
                }
                p_callback(null, result);
            }
            else
            {
                p_callback(new Error("request zhihu-daily api ('/news/:id') error:" + err.message), null);
            }
        });
    }
    else
    {
        p_callback(new Error("p_id has a wrong format."), null);
    }
};

/**
 * 从知乎日报服务器获取指定 Id 的日报，并存储到数据库。
 * @param  {String} p_id         指定的 Id。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.cacheStory = function (p_id, p_callback)
{
    async.retry({
        times: config.crawler.retry,
        interval: config.crawler.interval
    }, this.fetchStory.bind(this, p_id), function (err, res)
    {
        // 未成功抓取的日报，记录其 Id，并标记为 cached: false。
        if (err)
        {
            story.logUncachedStory(p_id, p_callback);
        }
        else
        {
            story.saveStory(res, p_callback);
        }
    });
};

/**
 * 从知乎日报服务器获取指定日期的日报，并存储到数据库。
 * @param  {String} p_date       指定的日期。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.cacheStories = function (p_date, p_callback)
{
    async.waterfall(
        [
            this.fetchStoryIndexes.bind(this, p_date),
            _cacheStoriesTask.bind(this)
        ],
        p_callback
    );
};

/**
 * 从知乎日报服务器获取日报并保存到数据库。
 */
function _cacheStoriesTask(p_indexes, p_callback)
{
    var result = { cached: [] };
    async.eachSeries(p_indexes.indexes, function (id, done)
    {
        this.cacheStory(id, function (err, res)
        {
            if (!err)
            {
                result.cached.push(id);
            }
            done();
        });
    }.bind(this), function ()
    {
        p_callback(null, result);
    });
}

/**
 * 从知乎日报服务器获取获取指定的图片。
 * @param  {String} p_url        指定的图片地址。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.fetchImage = function (p_url, p_res)
{
    if (!_.isFunction(p_callback)) return;
    // TODO:
};
<commit_msg>update config<commit_after>var _ = require("lodash");
var async = require("async");
var config = require("config");
var cheerio = require("cheerio");
var querystring = require("querystring");
var dailyRequest = require("request").defaults({
    baseUrl : config.zhihu_daily_api
});

var story = require("../story");
var utils = require("../utils");
const PREFIX = "/api/4/imgs/";
const MINDATE = utils.convertZhihuDateToMoment("20130520");

/**
 * 从知乎日报服务器获取指定日期的知乎日报索引（ID）。
 * @param  {String} p_date       指定的日期。如果小于 20130519，返回值 res 为 {}。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.fetchStoryIndexes = function (p_date, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    // 因知乎日报 API 返回的是指定日期的前一天的日报，
    // 所以要加一天才能获取指定日期的日报。
    var date = utils.nextZhihuDay(p_date);
    if (date)
    {
        if (utils.convertZhihuDateToMoment(date).isBefore(MINDATE))
        {
            // 20130519 之前是没有知乎日报的。
            p_callback(null, {});
        }
        else
        {
            dailyRequest.get({ url: "/news/before/" + date, json: true }, function (err, res, body)
            {
                if (!err && res.statusCode == 200)
                {
                    var indexes = body.stories.map(function (item)
                    {
                        return item.id;
                    });

                    p_callback(null, {
                        date: body.date,
                        indexes: indexes
                    });
                }
                else
                {
                    p_callback(new Error("request zhihu-daily api ('/news/before/:date') error:" + err.message), null);
                }
            });
        }
    }
    else
    {
        p_callback(new Error("p_date has a wrong format."), null);
    }
};

/**
 * 从知乎日报服务器获取指定 Id 的知乎日报。
 * @param  {String} p_id         指定的 Id。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.fetchStory = function (p_id, p_callback)
{
    if (!_.isFunction(p_callback)) return;

    // 首先检查 Id 是否为纯数字。
    if (/^\d+$/.test(p_id))
    {
        dailyRequest.get({ url: "/news/" + p_id, json: true }, function (err, res, body)
        {
            if (!err && res.statusCode == 200)
            {
                var result = {};
                result.id = body.id;
                result.title = body.title;

                // TODO: 图片暂时不入库，后面再说。
                result.image = PREFIX + querystring.escape(body.image);

                result.imageSource = body.image_source;
                result.shareURL = body.share_url;

                if (body.body)
                {
                    var $ = cheerio.load(body.body, { decodeEntities: false });
                    result.backgrounds = $(".headline>.headline-background .headline-background-link").map(function (i, e)
                    {
                        return {
                            href: $(e).attr("href"),
                            title : $(e).children(".heading").text(),
                            text : $(e).children(".heading-content").text()
                        };
                    }).get();

                    result.contents = $(".content-inner>.question").map(function (i, e)
                    {
                        var question = {};
                        question.title = $(e).children(".question-title").text();
                        question.answers = $(e).children(".answer").map(function (i, e)
                        {
                            // TODO: 图片暂时不入库，后面再说。
                            $(e).find(".content img").each(function (i, e)
                            {
                                var src = $(e).attr("src");
                                if (src != null && src != "")
                                {
                                    $(e).attr("src", PREFIX + querystring.escape(src));
                                }
                            });

                            // TODO: 图片暂时不入库，后面再说。
                            var avatar = $(e).find(".meta>.avatar").attr("src");
                            if (avatar != null && avatar != "")
                            {
                                avatar = PREFIX + querystring.escape(avatar);
                            }
                            else
                            {
                                avatar = "";
                            }

                            return {
                                avatar : avatar,
                                name: $(e).find(".meta>.author").text(),
                                bio : $(e).find(".meta>.bio").text(),
                                content : $(e).children(".content").html()
                            };
                        }).get();

                        var a = $(e).find(".view-more>a");
                        if (a.length > 0)
                        {
                            question.link = {
                                href : a.attr("href"),
                                text : a.text(),
                            };
                        }

                        return question;
                    }).get();
                }
                p_callback(null, result);
            }
            else
            {
                p_callback(new Error("request zhihu-daily api ('/news/:id') error:" + err.message), null);
            }
        });
    }
    else
    {
        p_callback(new Error("p_id has a wrong format."), null);
    }
};

/**
 * 从知乎日报服务器获取指定 Id 的日报，并存储到数据库。
 * @param  {String} p_id         指定的 Id。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.cacheStory = function (p_id, p_callback)
{
    async.retry({
        times: config.crawler.daily_retry,
        interval: config.crawler.daily_interval
    }, this.fetchStory.bind(this, p_id), function (err, res)
    {
        // 未成功抓取的日报，记录其 Id，并标记为 cached: false。
        if (err)
        {
            story.logUncachedStory(p_id, p_callback);
        }
        else
        {
            story.saveStory(res, p_callback);
        }
    });
};

/**
 * 从知乎日报服务器获取指定日期的日报，并存储到数据库。
 * @param  {String} p_date       指定的日期。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.cacheStories = function (p_date, p_callback)
{
    async.waterfall(
        [
            this.fetchStoryIndexes.bind(this, p_date),
            _cacheStoriesTask.bind(this)
        ],
        p_callback
    );
};

/**
 * 从知乎日报服务器获取日报并保存到数据库。
 */
function _cacheStoriesTask(p_indexes, p_callback)
{
    var result = { cached: [] };
    async.eachSeries(p_indexes.indexes, function (id, done)
    {
        this.cacheStory(id, function (err, res)
        {
            if (!err)
            {
                result.cached.push(id);
            }
            done();
        });
    }.bind(this), function ()
    {
        p_callback(null, result);
    });
}

/**
 * 从知乎日报服务器获取获取指定的图片。
 * @param  {String} p_url        指定的图片地址。
 * @param  {Function} p_callback 回调函数：function(err, res)。
 */
exports.fetchImage = function (p_url, p_res)
{
    if (!_.isFunction(p_callback)) return;
    // TODO:
};
<|endoftext|>


<commit_before>/**
 * Main controller for Ghost frontend
 */

/*global require, module */

var Ghost  = require('../../ghost'),
    api    = require('../api'),
    RSS    = require('rss'),
    _      = require('underscore'),
    errors = require('../errorHandling'),
    when   = require('when'),
    url    = require('url'),

    ghost  = new Ghost(),
    frontendControllers;

frontendControllers = {
    'homepage': function (req, res, next) {
        // Parse the page number
        var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
            postsPerPage = parseInt(ghost.settings('postsPerPage'), 10),
            options = {};

        // No negative pages
        if (isNaN(pageParam) || pageParam < 1) {
            //redirect to 404 page?
            return res.redirect('/');
        }
        options.page = pageParam;

        // Redirect '/page/1/' to '/' for all teh good SEO
        if (pageParam === 1 && req.route.path === '/page/:page/') {
            return res.redirect('/');
        }

        // No negative posts per page, must be number
        if (!isNaN(postsPerPage) && postsPerPage > 0) {
            options.limit = postsPerPage;
        }

        api.posts.browse(options).then(function (page) {
            var maxPage = page.pages;

            // A bit of a hack for situations with no content.
            if (maxPage === 0) {
                maxPage = 1;
                page.pages = 1;
            }

            // If page is greater than number of pages we have, redirect to last page
            if (pageParam > maxPage) {
                return res.redirect(maxPage === 1 ? '/' : ('/page/' + maxPage + '/'));
            }

            // Render the page of posts
            ghost.doFilter('prePostsRender', page.posts).then(function (posts) {
                res.render('index', {posts: posts, pagination: {page: page.page, prev: page.prev, next: page.next, limit: page.limit, total: page.total, pages: page.pages}});
            });
        }).otherwise(function (err) {
            return next(new Error(err));
        });
    },
    'single': function (req, res, next) {
        api.posts.read({'slug': req.params.slug}).then(function (post) {
            if (post) {
                ghost.doFilter('prePostsRender', post).then(function (post) {
                    res.render('post', {post: post});
                });
            } else {
                next();
            }

        }).otherwise(function (err) {
            return next(new Error(err));
        });
    },
    'rss': function (req, res, next) {
        // Initialize RSS
        var siteUrl = ghost.config().url,
            pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
            feed;
        //needs refact for multi user to not use first user as default
        api.users.read({id : 1}).then(function (user) {
            feed = new RSS({
                title: ghost.settings('title'),
                description: ghost.settings('description'),
                generator: 'Ghost v' + res.locals.version,
                author: user ? user.name : null,
                feed_url: url.resolve(siteUrl, '/rss/'),
                site_url: siteUrl,
                ttl: '60'
            });

            // No negative pages
            if (isNaN(pageParam) || pageParam < 1) {
                return res.redirect('/rss/');
            }

            if (pageParam === 1 && req.route.path === '/rss/:page/') {
                return res.redirect('/rss/');
            }

            api.posts.browse({page: pageParam}).then(function (page) {
                var maxPage = page.pages;

                // A bit of a hack for situations with no content.
                if (maxPage === 0) {
                    maxPage = 1;
                    page.pages = 1;
                }

                // If page is greater than number of pages we have, redirect to last page
                if (pageParam > maxPage) {
                    return res.redirect('/rss/' + maxPage + '/');
                }

                ghost.doFilter('prePostsRender', page.posts).then(function (posts) {
                    posts.forEach(function (post) {
                        var item = {
                                title:  _.escape(post.title),
                                guid: post.uuid,
                                url: siteUrl + '/' + post.slug + '/',
                                date: post.published_at
                            },
                            content = post.html;

                        //set img src to absolute url
                        content = content.replace(/src=["|'|\s]?([\w\/\?\$\.\+\-;%:@&=,_]+)["|'|\s]?/gi, function (match, p1) {
                            /*jslint unparam:true*/
                            p1 = url.resolve(siteUrl, p1);
                            return "src='" + p1 + "' ";
                        });
                        //set a href to absolute url
                        content = content.replace(/href=["|'|\s]?([\w\/\?\$\.\+\-;%:@&=,_]+)["|'|\s]?/gi, function (match, p1) {
                            /*jslint unparam:true*/
                            p1 = url.resolve(siteUrl, p1);
                            return "href='" + p1 + "' ";
                        });
                        item.description = content;
                        feed.item(item);
                    });
                    res.set('Content-Type', 'text/xml');
                    res.send(feed.xml());
                });
            });
        }).otherwise(function (err) {
            return next(new Error(err));
        });
    }
};

module.exports = frontendControllers;
<commit_msg>Render a page template if it exists in a theme for a post marked as page.<commit_after>/**
 * Main controller for Ghost frontend
 */

/*global require, module */

var Ghost  = require('../../ghost'),
    api    = require('../api'),
    RSS    = require('rss'),
    _      = require('underscore'),
    errors = require('../errorHandling'),
    when   = require('when'),
    url    = require('url'),

    ghost  = new Ghost(),
    frontendControllers;

frontendControllers = {
    'homepage': function (req, res, next) {
        // Parse the page number
        var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
            postsPerPage = parseInt(ghost.settings('postsPerPage'), 10),
            options = {};

        // No negative pages
        if (isNaN(pageParam) || pageParam < 1) {
            //redirect to 404 page?
            return res.redirect('/');
        }
        options.page = pageParam;

        // Redirect '/page/1/' to '/' for all teh good SEO
        if (pageParam === 1 && req.route.path === '/page/:page/') {
            return res.redirect('/');
        }

        // No negative posts per page, must be number
        if (!isNaN(postsPerPage) && postsPerPage > 0) {
            options.limit = postsPerPage;
        }

        api.posts.browse(options).then(function (page) {
            var maxPage = page.pages;

            // A bit of a hack for situations with no content.
            if (maxPage === 0) {
                maxPage = 1;
                page.pages = 1;
            }

            // If page is greater than number of pages we have, redirect to last page
            if (pageParam > maxPage) {
                return res.redirect(maxPage === 1 ? '/' : ('/page/' + maxPage + '/'));
            }

            // Render the page of posts
            ghost.doFilter('prePostsRender', page.posts).then(function (posts) {
                res.render('index', {posts: posts, pagination: {page: page.page, prev: page.prev, next: page.next, limit: page.limit, total: page.total, pages: page.pages}});
            });
        }).otherwise(function (err) {
            return next(new Error(err));
        });
    },
    'single': function (req, res, next) {
        api.posts.read({'slug': req.params.slug}).then(function (post) {
            if (post) {
                ghost.doFilter('prePostsRender', post).then(function (post) {
                    var paths = ghost.paths().availableThemes[ghost.settings('activeTheme')];
                    if (post.page && paths.hasOwnProperty('page')) {
                        res.render('page', {post: post});
                    } else {
                        res.render('post', {post: post});
                    }
                });
            } else {
                next();
            }

        }).otherwise(function (err) {
            return next(new Error(err));
        });
    },
    'rss': function (req, res, next) {
        // Initialize RSS
        var siteUrl = ghost.config().url,
            pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
            feed;
        //needs refact for multi user to not use first user as default
        api.users.read({id : 1}).then(function (user) {
            feed = new RSS({
                title: ghost.settings('title'),
                description: ghost.settings('description'),
                generator: 'Ghost v' + res.locals.version,
                author: user ? user.name : null,
                feed_url: url.resolve(siteUrl, '/rss/'),
                site_url: siteUrl,
                ttl: '60'
            });

            // No negative pages
            if (isNaN(pageParam) || pageParam < 1) {
                return res.redirect('/rss/');
            }

            if (pageParam === 1 && req.route.path === '/rss/:page/') {
                return res.redirect('/rss/');
            }

            api.posts.browse({page: pageParam}).then(function (page) {
                var maxPage = page.pages;

                // A bit of a hack for situations with no content.
                if (maxPage === 0) {
                    maxPage = 1;
                    page.pages = 1;
                }

                // If page is greater than number of pages we have, redirect to last page
                if (pageParam > maxPage) {
                    return res.redirect('/rss/' + maxPage + '/');
                }

                ghost.doFilter('prePostsRender', page.posts).then(function (posts) {
                    posts.forEach(function (post) {
                        var item = {
                                title:  _.escape(post.title),
                                guid: post.uuid,
                                url: siteUrl + '/' + post.slug + '/',
                                date: post.published_at
                            },
                            content = post.html;

                        //set img src to absolute url
                        content = content.replace(/src=["|'|\s]?([\w\/\?\$\.\+\-;%:@&=,_]+)["|'|\s]?/gi, function (match, p1) {
                            /*jslint unparam:true*/
                            p1 = url.resolve(siteUrl, p1);
                            return "src='" + p1 + "' ";
                        });
                        //set a href to absolute url
                        content = content.replace(/href=["|'|\s]?([\w\/\?\$\.\+\-;%:@&=,_]+)["|'|\s]?/gi, function (match, p1) {
                            /*jslint unparam:true*/
                            p1 = url.resolve(siteUrl, p1);
                            return "href='" + p1 + "' ";
                        });
                        item.description = content;
                        feed.item(item);
                    });
                    res.set('Content-Type', 'text/xml');
                    res.send(feed.xml());
                });
            });
        }).otherwise(function (err) {
            return next(new Error(err));
        });
    }
};

module.exports = frontendControllers;
<|endoftext|>


<commit_before>var Edde = {
	init: function () {
		var $document = $(document);
		$document.on('click', '.edde-hide-on-click', function () {
			$(this).hide();
		});
		$document.on('click', '.edde-clickable:not(.disabled)', function () {
			var $this = $(this);
			$this.addClass('disabled');
			Edde.execute($this.data('action'), Edde.crate($this.data('bind'))).always(function () {
				$this.removeClass('disabled');
			});
		});
		this.bind();
	},
	execute: function (url, parameterList) {
		return $.post(url, parameterList || {}).fail(function () {
			alert('General server error; this should be fixed by a developer.');
		}).done(function (data) {
			if (data.redirect) {
				window.location.replace(data.redirect);
			}
			if (data.selector) {
				$.each(data.selector, function (selector, value) {
					switch (value.action) {
						case 'replace':
							$(selector).replaceWith(value.source);
							break;
						case 'add':
							$(selector).append(value.source);
							break;
					}
				});
			}
			Edde.bind();
		});
	},
	bind: function () {
		$('.edde-text-input').each(function () {
			this.getValue = function () {
				return $(this).val();
			};
		});
	},
	crate: function (id) {
		var crate = {};
		if (id) {
			$('#' + id).find('.edde-value').each(function () {
				var $this = $(this);
				var dataClass = $this.data('schema');
				crate[dataClass] = crate[dataClass] ? crate[dataClass] : {};
				crate[dataClass][$this.data('property')] = this.getValue();
			});
		}
		return crate;
	}
};

$(document).ready(function () {
	Edde.init();
});
<commit_msg>- will this help?<commit_after>var Edde = {
	init: function () {
		var $document = $(document);
		$document.on('click', '.edde-hide-on-click', function () {
			$(this).hide();
		});
		$document.on('click', '.edde-clickable:not(.disabled)', function () {
			var $this = $(this);
			$this.addClass('disabled');
			Edde.execute($this.data('action'), Edde.crate($this.data('bind'))).always(function () {
				$this.removeClass('disabled');
			});
		});
		this.bind();
	},
	execute: function (url, parameterList) {
		return $.post(url, parameterList || {}).fail(function () {
			alert('General server error; this should be fixed by a developer.');
		}).done(function (data) {
			if (data.redirect) {
				window.location.replace(data.redirect);
			}
			if (data.selector) {
				$.each(data.selector, function (selector, value) {
					switch (value.action) {
						case 'replace':
							$(selector).replaceWith(value.source);
							break;
						case 'add':
							$(selector).append(value.source);
							$(selector).show();
							break;
					}
				});
			}
			Edde.bind();
		});
	},
	bind: function () {
		$('.edde-text-input').each(function () {
			this.getValue = function () {
				return $(this).val();
			};
		});
	},
	crate: function (id) {
		var crate = {};
		if (id) {
			$('#' + id).find('.edde-value').each(function () {
				var $this = $(this);
				var dataClass = $this.data('schema');
				crate[dataClass] = crate[dataClass] ? crate[dataClass] : {};
				crate[dataClass][$this.data('property')] = this.getValue();
			});
		}
		return crate;
	}
};

$(document).ready(function () {
	Edde.init();
});
<|endoftext|>


<commit_before>var _               = require('underscore'),
    when            = require('when'),
    errors          = require('../../errorHandling'),
    client          = require('../../models/base').client,
    knex            = require('../../models/base').knex,
    sequence        = require('when/sequence'),

    defaultSettings = require('../default-settings'),
    Settings        = require('../../models/settings').Settings,
    fixtures        = require('../fixtures'),
    schema          = require('../schema').tables,

    initialVersion  = '000',
    schemaTables    = _.keys(schema),
    defaultDatabaseVersion,

    init,
    reset,
    migrateUp,
    migrateUpFreshDb,
    getTables;

// Default Database Version
// The migration version number according to the hardcoded default settings
// This is the version the database should be at or migrated to
function getDefaultDatabaseVersion() {
    if (!defaultDatabaseVersion) {
        // This be the current version according to the software
        defaultDatabaseVersion = _.find(defaultSettings.core, function (setting) {
            return setting.key === 'databaseVersion';
        }).defaultValue;
    }

    return defaultDatabaseVersion;
}

// Database Current Version
// The migration version number according to the database
// This is what the database is currently at and may need to be updated
function getDatabaseVersion() {
    return knex.schema.hasTable('settings').then(function (exists) {
        // Check for the current version from the settings table
        if (exists) {
            // Temporary code to deal with old databases with currentVersion settings
            return knex('settings')
                .where('key', 'databaseVersion')
                .orWhere('key', 'currentVersion')
                .select('value')
                .then(function (versions) {
                    var databaseVersion = _.reduce(versions, function (memo, version) {
                        if (isNaN(version.value)) {
                            errors.throwError('Database version is not recognised');
                        }
                        return parseInt(version.value, 10) > parseInt(memo, 10) ? version.value : memo;
                    }, initialVersion);

                    if (!databaseVersion || databaseVersion.length === 0) {
                        // we didn't get a response we understood, assume initialVersion
                        databaseVersion = initialVersion;
                    }

                    return databaseVersion;
                });
        }
        throw new Error('Settings table does not exist');
    });
}

function setDatabaseVersion() {
    return knex('settings')
        .where('key', 'databaseVersion')
        .update({ 'value': defaultDatabaseVersion });
}

function createTable(table) {
    return knex.schema.createTable(table, function (t) {
        var column,
            columnKeys = _.keys(schema[table]);
        _.each(columnKeys, function (key) {
            if (schema[table][key].hasOwnProperty('maxlength')) {
                column = t[schema[table][key].type](key, schema[table][key].maxlength);
            } else {
                column = t[schema[table][key].type](key);
            }
            if (schema[table][key].hasOwnProperty('nullable') && schema[table][key].nullable === true) {
                column.nullable();
            } else {
                column.notNullable();
            }
            if (schema[table][key].hasOwnProperty('primary') && schema[table][key].primary === true) {
                column.primary();
            }
            if (schema[table][key].hasOwnProperty('unique') && schema[table][key].unique) {
                column.unique();
            }
            if (schema[table][key].hasOwnProperty('unsigned') && schema[table][key].unsigned) {
                column.unsigned();
            }
            if (schema[table][key].hasOwnProperty('references') && schema[table][key].hasOwnProperty('inTable')) {
                //check if table exists?
                column.references(schema[table][key].references);
                column.inTable(schema[table][key].inTable);
            }
            if (schema[table][key].hasOwnProperty('defaultTo')) {
                column.defaultTo(schema[table][key].defaultTo);
            }
        });
    });
}

function deleteTable(table) {
    return knex.schema.dropTableIfExists(table);
}

function getDeleteCommands(oldTables, newTables) {
    var deleteTables = _.difference(oldTables, newTables);
    if (!_.isEmpty(deleteTables)) {
        return _.map(deleteTables, function (table) {
            return function () {
                return deleteTable(table);
            };
        });
    }
}

function getAddCommands(oldTables, newTables) {
    var addTables = _.difference(newTables, oldTables);
    if (!_.isEmpty(addTables)) {
        return _.map(addTables, function (table) {
            return function () {
                return createTable(table);
            };
        });
    }
}

function getTablesFromSqlite3() {
    return knex.raw("select * from sqlite_master where type = 'table'").then(function (response) {
        return _.reject(_.pluck(response[0], 'tbl_name'), function (name) {
            return name === 'sqlite_sequence';
        });
    });
}

function getTablesFromPgSQL() {
    return knex.raw("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'").then(function (response) {
        return _.flatten(_.map(response[0], function (entry) {
            return _.values(entry);
        }));
    });
}

function getTablesFromMySQL() {
    return knex.raw("show tables").then(function (response) {
        return _.flatten(_.map(response[0], function (entry) {
            return _.values(entry);
        }));
    });
}

// Check for whether data is needed to be bootstrapped or not
init = function () {
    var self = this;
    // There are 4 possibilities:
    // 1. The database exists and is up-to-date
    // 2. The database exists but is out of date
    // 3. The database exists but the currentVersion setting does not or cannot be understood
    // 4. The database has not yet been created
    return getDatabaseVersion().then(function (databaseVersion) {
        var defaultVersion = getDefaultDatabaseVersion();
        if (databaseVersion === defaultVersion) {
            // 1. The database exists and is up-to-date
            return when.resolve();
        }
        if (databaseVersion < defaultVersion) {
            // 2. The database exists but is out of date
            // Migrate to latest version
            return self.migrateUp().then(function () {
                // Finally update the databases current version
                return setDatabaseVersion();
            });
        }
        if (databaseVersion > defaultVersion) {
            // 3. The database exists but the currentVersion setting does not or cannot be understood
            // In this case we don't understand the version because it is too high
            errors.logErrorAndExit(
                'Your database is not compatible with this version of Ghost',
                'You will need to create a new database'
            );
        }
    }, function (err) {
        if (err.message || err === 'Settings table does not exist') {
            // 4. The database has not yet been created
            // Bring everything up from initial version.
            return self.migrateUpFreshDb();
        }
        // 3. The database exists but the currentVersion setting does not or cannot be understood
        // In this case the setting was missing or there was some other problem
        errors.logErrorAndExit('There is a problem with the database', err.message || err);
    });
};

// ### Reset
// Delete all tables from the database in reverse order
reset = function () {
    var tables = [];
    tables = _.map(schemaTables, function (table) {
        return function () {
            return deleteTable(table);
        };
    }).reverse();

    return sequence(tables);
};

// Only do this if we have no database at all
migrateUpFreshDb = function () {
    var tables = [];
    tables = _.map(schemaTables, function (table) {
        return function () {
            return createTable(table);
        };
    });

    return sequence(tables).then(function () {
        // Load the fixtures
        return fixtures.populateFixtures().then(function () {
            // Initialise the default settings
            return Settings.populateDefaults();
        });
    });
};

// Migrate from a specific version to the latest
migrateUp = function () {
    return getTables().then(function (oldTables) {
        var deleteCommands = getDeleteCommands(oldTables, schemaTables),
            addCommands = getAddCommands(oldTables, schemaTables),
            commands = [];
        if (!_.isEmpty(deleteCommands)) {
            commands = commands.concat(deleteCommands);
        }
        if (!_.isEmpty(addCommands)) {
            commands = commands.concat(addCommands);
        }
        if (!_.isEmpty(commands)) {
            return sequence(commands);
        }
        return;
    });
};

getTables = function () {
    if (client === 'sqlite3') {
        return getTablesFromSqlite3();
    }
    if (client === 'mysql') {
        return getTablesFromMySQL();
    }
    if (client === 'pg') {
        return getTablesFromPgSQL();
    }
    return when.reject("No support for database client " + client);
};

module.exports = {
    getDatabaseVersion: getDatabaseVersion,
    init: init,
    reset: reset,
    migrateUp: migrateUp,
    migrateUpFreshDb: migrateUpFreshDb
};<commit_msg>fix table scan for pg migrations<commit_after>var _               = require('underscore'),
    when            = require('when'),
    errors          = require('../../errorHandling'),
    client          = require('../../models/base').client,
    knex            = require('../../models/base').knex,
    sequence        = require('when/sequence'),

    defaultSettings = require('../default-settings'),
    Settings        = require('../../models/settings').Settings,
    fixtures        = require('../fixtures'),
    schema          = require('../schema').tables,

    initialVersion  = '000',
    schemaTables    = _.keys(schema),
    defaultDatabaseVersion,

    init,
    reset,
    migrateUp,
    migrateUpFreshDb,
    getTables;

// Default Database Version
// The migration version number according to the hardcoded default settings
// This is the version the database should be at or migrated to
function getDefaultDatabaseVersion() {
    if (!defaultDatabaseVersion) {
        // This be the current version according to the software
        defaultDatabaseVersion = _.find(defaultSettings.core, function (setting) {
            return setting.key === 'databaseVersion';
        }).defaultValue;
    }

    return defaultDatabaseVersion;
}

// Database Current Version
// The migration version number according to the database
// This is what the database is currently at and may need to be updated
function getDatabaseVersion() {
    return knex.schema.hasTable('settings').then(function (exists) {
        // Check for the current version from the settings table
        if (exists) {
            // Temporary code to deal with old databases with currentVersion settings
            return knex('settings')
                .where('key', 'databaseVersion')
                .orWhere('key', 'currentVersion')
                .select('value')
                .then(function (versions) {
                    var databaseVersion = _.reduce(versions, function (memo, version) {
                        if (isNaN(version.value)) {
                            errors.throwError('Database version is not recognised');
                        }
                        return parseInt(version.value, 10) > parseInt(memo, 10) ? version.value : memo;
                    }, initialVersion);

                    if (!databaseVersion || databaseVersion.length === 0) {
                        // we didn't get a response we understood, assume initialVersion
                        databaseVersion = initialVersion;
                    }

                    return databaseVersion;
                });
        }
        throw new Error('Settings table does not exist');
    });
}

function setDatabaseVersion() {
    return knex('settings')
        .where('key', 'databaseVersion')
        .update({ 'value': defaultDatabaseVersion });
}

function createTable(table) {
    return knex.schema.createTable(table, function (t) {
        var column,
            columnKeys = _.keys(schema[table]);
        _.each(columnKeys, function (key) {
            if (schema[table][key].hasOwnProperty('maxlength')) {
                column = t[schema[table][key].type](key, schema[table][key].maxlength);
            } else {
                column = t[schema[table][key].type](key);
            }
            if (schema[table][key].hasOwnProperty('nullable') && schema[table][key].nullable === true) {
                column.nullable();
            } else {
                column.notNullable();
            }
            if (schema[table][key].hasOwnProperty('primary') && schema[table][key].primary === true) {
                column.primary();
            }
            if (schema[table][key].hasOwnProperty('unique') && schema[table][key].unique) {
                column.unique();
            }
            if (schema[table][key].hasOwnProperty('unsigned') && schema[table][key].unsigned) {
                column.unsigned();
            }
            if (schema[table][key].hasOwnProperty('references') && schema[table][key].hasOwnProperty('inTable')) {
                //check if table exists?
                column.references(schema[table][key].references);
                column.inTable(schema[table][key].inTable);
            }
            if (schema[table][key].hasOwnProperty('defaultTo')) {
                column.defaultTo(schema[table][key].defaultTo);
            }
        });
    });
}

function deleteTable(table) {
    return knex.schema.dropTableIfExists(table);
}

function getDeleteCommands(oldTables, newTables) {
    var deleteTables = _.difference(oldTables, newTables);
    if (!_.isEmpty(deleteTables)) {
        return _.map(deleteTables, function (table) {
            return function () {
                return deleteTable(table);
            };
        });
    }
}

function getAddCommands(oldTables, newTables) {
    var addTables = _.difference(newTables, oldTables);
    if (!_.isEmpty(addTables)) {
        return _.map(addTables, function (table) {
            return function () {
                return createTable(table);
            };
        });
    }
}

function getTablesFromSqlite3() {
    return knex.raw("select * from sqlite_master where type = 'table'").then(function (response) {
        return _.reject(_.pluck(response[0], 'tbl_name'), function (name) {
            return name === 'sqlite_sequence';
        });
    });
}

function getTablesFromPgSQL() {
    return knex.raw("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'").then(function (response) {
        return _.flatten(_.pluck(response.rows, 'table_name'));
    });
}

function getTablesFromMySQL() {
    return knex.raw("show tables").then(function (response) {
        return _.flatten(_.map(response[0], function (entry) {
            return _.values(entry);
        }));
    });
}

// Check for whether data is needed to be bootstrapped or not
init = function () {
    var self = this;
    // There are 4 possibilities:
    // 1. The database exists and is up-to-date
    // 2. The database exists but is out of date
    // 3. The database exists but the currentVersion setting does not or cannot be understood
    // 4. The database has not yet been created
    return getDatabaseVersion().then(function (databaseVersion) {
        var defaultVersion = getDefaultDatabaseVersion();
        if (databaseVersion === defaultVersion) {
            // 1. The database exists and is up-to-date
            return when.resolve();
        }
        if (databaseVersion < defaultVersion) {
            // 2. The database exists but is out of date
            // Migrate to latest version
            return self.migrateUp().then(function () {
                // Finally update the databases current version
                return setDatabaseVersion();
            });
        }
        if (databaseVersion > defaultVersion) {
            // 3. The database exists but the currentVersion setting does not or cannot be understood
            // In this case we don't understand the version because it is too high
            errors.logErrorAndExit(
                'Your database is not compatible with this version of Ghost',
                'You will need to create a new database'
            );
        }
    }, function (err) {
        if (err.message || err === 'Settings table does not exist') {
            // 4. The database has not yet been created
            // Bring everything up from initial version.
            return self.migrateUpFreshDb();
        }
        // 3. The database exists but the currentVersion setting does not or cannot be understood
        // In this case the setting was missing or there was some other problem
        errors.logErrorAndExit('There is a problem with the database', err.message || err);
    });
};

// ### Reset
// Delete all tables from the database in reverse order
reset = function () {
    var tables = [];
    tables = _.map(schemaTables, function (table) {
        return function () {
            return deleteTable(table);
        };
    }).reverse();

    return sequence(tables);
};

// Only do this if we have no database at all
migrateUpFreshDb = function () {
    var tables = [];
    tables = _.map(schemaTables, function (table) {
        return function () {
            return createTable(table);
        };
    });

    return sequence(tables).then(function () {
        // Load the fixtures
        return fixtures.populateFixtures().then(function () {
            // Initialise the default settings
            return Settings.populateDefaults();
        });
    });
};

// Migrate from a specific version to the latest
migrateUp = function () {
    return getTables().then(function (oldTables) {
        var deleteCommands = getDeleteCommands(oldTables, schemaTables),
            addCommands = getAddCommands(oldTables, schemaTables),
            commands = [];
        if (!_.isEmpty(deleteCommands)) {
            commands = commands.concat(deleteCommands);
        }
        if (!_.isEmpty(addCommands)) {
            commands = commands.concat(addCommands);
        }
        if (!_.isEmpty(commands)) {
            return sequence(commands);
        }
        return;
    });
};

getTables = function () {
    if (client === 'sqlite3') {
        return getTablesFromSqlite3();
    }
    if (client === 'mysql') {
        return getTablesFromMySQL();
    }
    if (client === 'pg') {
        return getTablesFromPgSQL();
    }
    return when.reject("No support for database client " + client);
};

module.exports = {
    getDatabaseVersion: getDatabaseVersion,
    init: init,
    reset: reset,
    migrateUp: migrateUp,
    migrateUpFreshDb: migrateUpFreshDb
};<|endoftext|>


<commit_before>module.exports = {
  'id': -1,
  'title': 'Management Dashboard',
  'builtin': true,
	'charts' : [{
			'title'      : 'Polio Cases YTD',
			'section'    : 'impact',
			'indicators' : [168],
			'startOf'    : 'year',
			'timeRange'  : {
				years : 2
			},
		}, {
			'title'      : 'Under-Immunized Children',
			'section'    : 'impact',
			'indicators' : [431,432,433],
			'startOf'    : 'quarter',
			'timeRange'  : {
				years : 3
			}
		}, {
			'title'      : 'Missed Children',
			'section'    : 'performance',
			'indicators' : [166,164,167,165],
			'timeRange'  : {
				months : 12
			}
		}, {
			'title'      : 'Missed Children by Province',
			'section'    : 'performance',
      'type'       : 'ChoroplethMap',
      'locations'  : 'sublocations',
      'timeRange'  : 0,
			'indicators' : [475],
		}, {
			'title'      : 'Conversions',
			'section'    : 'performance',
			'indicators' : [187,189],
			'timeRange'  : {
				months : 12
			}
		}, {
			'title'      : 'Transit Points',
			'section'    : 'performance',
      'timeRange'  : 0,
			'indicators' : [175,176,177,204],
		}, {
			'title'      : 'FLW\'s Capacity to Perform',
			'indicators' : [178,179,239,184,185,226],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Vaccinators',
			'indicators' : [180,230,228],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Supply',
			'indicators' : [194,219,173,172],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Routine',
			'indicators' : [245,236],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Resources',
			'indicators' : [169,233],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Number of Inaccessible Children',
			'section'    : 'access',
			'indicators' : [158],
			'timeRange'  : {
				months : 12
			}
		}, {
			'title'      : 'Districts with Access Plans',
			'section'    : 'access',
			'indicators' : [174],
      'timeRange'  : 0
		}, {
			'title'      : 'Inaccessibility Breakdown',
			'section'    : 'access',
			'indicators' : [442,443,444,445,446,447,448,449,450],
      'timeRange'  : 0
		}, {
			'title'			 : 'Microplans',
			'indicators' : [27,28],
			'timeRange'  : 0
		}, {
      'title'      : 'Vaccinated Children by Province',
      'section'    : 'performance',
      'type'       : 'ChoroplethMap',
      'locations'  : 'sublocations',
      'timeRange'  : 0,
			'indicators' : [177]
		}
	]
};
<commit_msg>#462 add indicator for access challenged district in management.js<commit_after>module.exports = {
  'id': -1,
  'title': 'Management Dashboard',
  'builtin': true,
	'charts' : [{
			'title'      : 'Polio Cases YTD',
			'section'    : 'impact',
			'indicators' : [168],
			'startOf'    : 'year',
			'timeRange'  : {
				years : 2
			},
		}, {
			'title'      : 'Under-Immunized Children',
			'section'    : 'impact',
			'indicators' : [431,432,433],
			'startOf'    : 'quarter',
			'timeRange'  : {
				years : 3
			}
		}, {
			'title'      : 'Missed Children',
			'section'    : 'performance',
			'indicators' : [166,164,167,165],
			'timeRange'  : {
				months : 12
			}
		}, {
			'title'      : 'Missed Children by Province',
			'section'    : 'performance',
      'type'       : 'ChoroplethMap',
      'locations'  : 'sublocations',
      'timeRange'  : 0,
			'indicators' : [475],
		}, {
			'title'      : 'Conversions',
			'section'    : 'performance',
			'indicators' : [187,189],
			'timeRange'  : {
				months : 12
			}
		}, {
			'title'      : 'Transit Points',
			'section'    : 'performance',
      'timeRange'  : 0,
			'indicators' : [175,176,177,204],
		}, {
			'title'      : 'FLW\'s Capacity to Perform',
			'indicators' : [178,179,239,184,185,226],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Vaccinators',
			'indicators' : [180,230,228],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Supply',
			'indicators' : [194,219,173,172],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Routine',
			'indicators' : [245,236],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Resources',
			'indicators' : [169,233],
			'timeRange'  : {
				months : 4
			}
		}, {
			'title'      : 'Number of Inaccessible Children',
			'section'    : 'access',
			'indicators' : [158],
			'timeRange'  : {
				months : 12
			}
		}, {
			'title'      : 'Districts with Access Plans',
			'section'    : 'access',
			'indicators' : [174],
      'timeRange'  : 0
		}, {
			'title'      : 'Inaccessibility Breakdown',
			'section'    : 'access',
			'indicators' : [442,443,444,445,446,447,448,449,450],
      'timeRange'  : 0
		}, {
			'title'			 : 'Microplans',
			'indicators' : [27,28],
			'timeRange'  : 0
		}, {
      'title'      : 'Vaccinated Children by Province',
      'section'    : 'performance',
      'type'       : 'ChoroplethMap',
      'locations'  : 'sublocations',
      'timeRange'  : 0,
			'indicators' : [177]
    }, {
      'title'      : 'Access Challenged District by Province',
      'section'    : 'performance',
      'type'       : 'ChoroplethMap',
      'locations'  : 'sublocations',
      'timeRange'  : 0,
			'indicators' : [203]
		}
	]
};
<|endoftext|>


<commit_before>/* @flow */

import Promise from 'bluebird'
import fs from 'fs-extra'
import path from 'path'
import rimraf from 'rimraf'

import * as conflictHelpers from './conflict'
import { SyncDirTestHelpers } from './sync_dir'

import { TMP_DIR_NAME } from '../../src/local/constants'
import Local from '../../src/local'

import type { ChokidarFSEvent } from '../../src/local/chokidar_event'

Promise.promisifyAll(fs)
const rimrafAsync = Promise.promisify(rimraf)

export function posixifyPath (localPath: string): string {
  return localPath.split(path.sep).join(path.posix.sep)
}

async function tree (rootPath: string): Promise<string[]> {
  const dirsToRead = [rootPath]
  const relPaths = []
  const makeRelative = (absPath: string) => posixifyPath(absPath.slice(rootPath.length + path.sep.length))

  while (true) {
    const dir = dirsToRead.shift()
    if (dir == null) break

    for (const name of await fs.readdirAsync(dir)) {
      if (name === TMP_DIR_NAME) continue

      const absPath = path.join(dir, name)
      const stat = await fs.statAsync(absPath)
      let relPath = makeRelative(absPath)

      if (stat.isDirectory()) {
        dirsToRead.push(absPath)
        relPath = relPath + path.posix.sep
      }

      relPaths.push(relPath)
    }
  }

  return relPaths.sort()
}

export class LocalTestHelpers {
  local: Local
  syncDir: SyncDirTestHelpers

  constructor (local: Local) {
    this.local = local
    this.syncDir = new SyncDirTestHelpers(local.syncPath)
  }

  get syncPath (): string {
    return path.normalize(this.local.syncPath)
  }

  get trashPath (): string {
    return path.join(this.local.tmpPath, '.test-trash')
  }

  async clean () {
    for (const pattern of ['*', '.*']) {
      await rimrafAsync(path.join(this.syncPath, pattern))
    }
  }

  async trashFunc (paths: string[]): Promise<void> {
    for (const src of paths) {
      const dst = path.join(this.trashPath, path.basename(src))
      try {
        await fs.renameAsync(src, dst)
      } catch (err) {
        throw err
      }
    }
  }

  async setupTrash () {
    await fs.emptyDir(this.trashPath)
    this.local._trash = this.trashFunc
  }

  async tree (): Promise<string[]> {
    let trashContents
    try {
      trashContents = await tree(this.trashPath)
    } catch (err) {
      if (err.code !== 'ENOENT') throw err
      throw new Error(
        'You must call and await helpers.local.setupTrash() (e.g. in a ' +
        'beforeEach block) before calling helpers.local.tree() in a test'
      )
    }
    return trashContents
      .map(relPath => path.posix.join('/Trash', relPath))
      .concat(await tree(this.syncPath))
      .map(conflictHelpers.ellipsizeDate)
  }

  async treeWithoutTrash () {
    return (await this.tree())
      .filter(p => !p.startsWith('/Trash/'))
  }

  async simulateEvents (events: ChokidarFSEvent[]) {
    return this.local.watcher.onFlush(events)
  }
}
<commit_msg>test/helpers: Sort local tree using localeCompare()<commit_after>/* @flow */

import Promise from 'bluebird'
import fs from 'fs-extra'
import path from 'path'
import rimraf from 'rimraf'

import * as conflictHelpers from './conflict'
import { SyncDirTestHelpers } from './sync_dir'

import { TMP_DIR_NAME } from '../../src/local/constants'
import Local from '../../src/local'

import type { ChokidarFSEvent } from '../../src/local/chokidar_event'

Promise.promisifyAll(fs)
const rimrafAsync = Promise.promisify(rimraf)

export function posixifyPath (localPath: string): string {
  return localPath.split(path.sep).join(path.posix.sep)
}

async function tree (rootPath: string): Promise<string[]> {
  const dirsToRead = [rootPath]
  const relPaths = []
  const makeRelative = (absPath: string) => posixifyPath(absPath.slice(rootPath.length + path.sep.length))

  while (true) {
    const dir = dirsToRead.shift()
    if (dir == null) break

    for (const name of await fs.readdirAsync(dir)) {
      if (name === TMP_DIR_NAME) continue

      const absPath = path.join(dir, name)
      const stat = await fs.statAsync(absPath)
      let relPath = makeRelative(absPath)

      if (stat.isDirectory()) {
        dirsToRead.push(absPath)
        relPath = relPath + path.posix.sep
      }

      relPaths.push(relPath)
    }
  }

  return relPaths.sort((a, b) => a.localeCompare(b))
}

export class LocalTestHelpers {
  local: Local
  syncDir: SyncDirTestHelpers

  constructor (local: Local) {
    this.local = local
    this.syncDir = new SyncDirTestHelpers(local.syncPath)
  }

  get syncPath (): string {
    return path.normalize(this.local.syncPath)
  }

  get trashPath (): string {
    return path.join(this.local.tmpPath, '.test-trash')
  }

  async clean () {
    for (const pattern of ['*', '.*']) {
      await rimrafAsync(path.join(this.syncPath, pattern))
    }
  }

  async trashFunc (paths: string[]): Promise<void> {
    for (const src of paths) {
      const dst = path.join(this.trashPath, path.basename(src))
      try {
        await fs.renameAsync(src, dst)
      } catch (err) {
        throw err
      }
    }
  }

  async setupTrash () {
    await fs.emptyDir(this.trashPath)
    this.local._trash = this.trashFunc
  }

  async tree (): Promise<string[]> {
    let trashContents
    try {
      trashContents = await tree(this.trashPath)
    } catch (err) {
      if (err.code !== 'ENOENT') throw err
      throw new Error(
        'You must call and await helpers.local.setupTrash() (e.g. in a ' +
        'beforeEach block) before calling helpers.local.tree() in a test'
      )
    }
    return trashContents
      .map(relPath => path.posix.join('/Trash', relPath))
      .concat(await tree(this.syncPath))
      .map(conflictHelpers.ellipsizeDate)
  }

  async treeWithoutTrash () {
    return (await this.tree())
      .filter(p => !p.startsWith('/Trash/'))
  }

  async simulateEvents (events: ChokidarFSEvent[]) {
    return this.local.watcher.onFlush(events)
  }
}
<|endoftext|>