<commit_before>// takes an IR function object and returns a list of Scratch blocks

module.exports.ffi = {};

module.exports.generateFunctionHat = function(functionContext, func) {
	var spec = func.funcName;
	var inputs = [];
	var defaults = [];

	functionContext.params = [];

	for(var i = 0; i < func.paramList.length; ++i) {
		var pName = "param" + i;

		if(func.paramList[i][1]) {
			pName = func.paramList[i][1].slice(1);
			inputs.push(pName);
			functionContext.params.push("%"+pName);
		} else {
			inputs.push(pName);
			functionContext.params.push(pName);
		}

		defaults.push(defaultForType(func.paramList[i][0]));
		spec += " "+specifierForType(func.paramList[i][0]);
	}

	return ["procDef", spec, inputs, defaults, false];

}

module.exports.compileFunction = function(func, IR) {
	var functionContext = {
		locals: {},
		localTypes: {},
		globalLocalDepth: 0,
		scopedLocalDepth: 0,
		params: [],
		gotoInit: false,
		globalToFree: 0,
		scopeToFree: 0,
		scoped: false,
		globals: IR.globals,
		rootGlobal: IR.rootGlobal
	}

	var blockList = [module.exports.generateFunctionHat(functionContext, func)];

	if(func.inGotoComplex) {
		blockList = blockList.concat(initGotoComplex());
	}

	for(var i = 0; i < func.code.length;) {
		var iGain = 1;

		var hasGotoComplex = functionContext.gotoComplex && functionContext.gotoComplex.okToUse && functionContext.gotoComplex.active; // this MUST be before compileInstruction for branching to work

		// optimize out alloca calls
		if(func.code[i].type == "set" && func.code[i].computation == [] && func.code[i].value == 0 &&
			func.code[i+1].type == "store" && func.code[i+1].destination.value == func.code[i].name) {

			func.code[i].value = func.code[i+1].src.value;
			iGain++;
		}

		// optimize out icmp in conditional branch
		if(func.code[i].type == "set" && func.code[i].val.type == "comparison" &&
			func.code[i+1].type == "branch" && func.code[i+1].conditional && func.code[i+1].condition == func.code[i].name) {

			func.code[i] = {
				type: "branch",
				conditional: true,
				dest: func.code[i+1].dest,
				falseDest: func.code[i+1].falseDest,
				condition: icmpBlock(functionContext, func.code[i]),
				rawCondition: true
			};

			iGain++;
		}

		var instruction = compileInstruction(functionContext, func.code[i]);

		if(!functionContext.gotoInit && functionContext.gotoComplex && functionContext.gotoComplex.okToUse) {
			blockList = blockList.concat([functionContext.gotoComplex.forever]);
			functionContext.gotoInit = true;
		}
		if(hasGotoComplex) {
			if(functionContext.gotoComplex.currentContext[2]) {
				functionContext.gotoComplex.currentContext[2] =
					functionContext.gotoComplex.currentContext[2].concat(instruction);
			} else {
				functionContext.gotoComplex.currentContext[2] = instruction;
			}
		} else {
			blockList = blockList.concat(instruction);
		}

		i += iGain;
	}

	//blockList = blockList.concat(returnBlock());

	return blockList;
}

function compileInstruction(ctx, block) {
	if(block.type == "call") {
		// calling a (potentially foreign) function
		return callBlock(ctx, block);
	} else if(block.type == "ffi") {
		// FFI block
		// load the code from the options
		return module.exports.ffi[block.ffiBlock];
	} else if(block.type == "set") {
		var val = 0;
		if(!block.val.vtype) console.log(block.val);
		var type = block.val.vtype || "";

		if(block.val.type == "return value") {
			val = ["readVariable", "return value"];
		} else if(block.val.type == "variable") {
			val = fetchByName(ctx, block.val.name, block.val.vtype);
			type = block.val.vtype;
		} else if(block.val.type == "arithmetic") {
			val = [block.val.operation, fetchByName(ctx, block.val.operand1), fetchByName(ctx, block.val.operand2)];
		} else if(block.val.type == "comparison") {
			val = icmpBlock(ctx, val);
		} else if(block.val.type == "sext") {
			val = signExtend(ctx, block.val);
		} else if(block.val.type == "addressOf") { // todo: full getelementptr implementation
			val = addressOf(ctx, block.val.base.name, block.val.offset);
		} else {
			console.log("Unknown equality in backend");
			console.log(block.val);
		}

		return compileInstruction(ctx, block.computation)
				.concat(allocateLocal(ctx, val, block.name, type));
	} else if(block.type == "ret") {
		return returnBlock(ctx, block.value);
	} else if(block.type == "store") {
		return dereferenceAndSet(ctx, block.destination.value, block.src.value);
	} else if(block.type == "gotoComplex") {
		ctx.gotoComplex = {
			context: [],
			okToUse: false,
			forever: ["doForever", []],
			active: true
		}

		//return [ctx.gotoComplex.forever];
	} else if(block.type == "label") {
		if(ctx.scoped) {
			ctx.gotoComplex.currentContext[2] =
				ctx.gotoComplex.currentContext[2].concat(freeLocals(ctx));
		}

		ctx.scoped = true;

		var chunk = ["doIfElse", ["=", getCurrentLabel(), block.label], [], []];

		ctx.gotoComplex.okToUse = true;
		ctx.gotoComplex.active = true;

		if(ctx.gotoComplex.currentContext) {
			ctx.gotoComplex.currentContext[3] = [chunk];
			ctx.gotoComplex.currentContext = ctx.gotoComplex.currentContext[3][0];
		} else {
			ctx.gotoComplex.currentContext = chunk;
			ctx.gotoComplex.context = ctx.gotoComplex.currentContext;
			ctx.gotoComplex.forever[1] = [ctx.gotoComplex.context];
		}

	} else if(block.type == "branch") {
		ctx.gotoComplex.active = false;

		if(block.conditional) {
			var cond = block.rawCondition ? block.condition : ["=", fetchByName(ctx, block.condition), 1];

			return [
				["doIfElse", cond, absoluteBranch(block.dest.slice(1)), absoluteBranch(block.falseDest.slice(1))]
			];
		} else {
			return absoluteBranch(block.dest);
		}
	}

	return [];
}

// fixme: stub
function defaultForType(type) {
	return 0;
}

// fixme: stub
function specifierForType(type) {
	return "%s";
}

// fixme: stub
function formatValue(ctx, type, value) {
	if(typeof value == "object") {
		if(value.type == "getelementptr") {
			// fixme: necessary and proper implementation
			return addressOf(ctx, value.base.val);
		}
	}

	if(value[0] == '%') {
		return fetchByName(ctx, value);
	}

	return value;
}

function getOffset(ctx, value) {
	return ctx.globalLocalDepth + ctx.scopedLocalDepth - ctx.locals[value];
}

function stackPtr() {
	return ["readVariable", "sp"];
}

function stackPosFromOffset(offset) {
	// optimize zero-index
	/*if(offset == 0) {
		return stackPtr();
	}*/

	return ["+", stackPtr(), offset];
}

// higher-level code generation

function allocateLocal(ctx, val, name, type) {
	if(name) {
		var depth = 0;

		if(ctx.scoped) {
			depth = ctx.globalLocalDepth + (ctx.scopedLocalDepth++);
		} else {
			depth = ctx.globalLocalDepth++;
		}

		ctx.locals[name] = depth;
		ctx.localTypes[name] = type;
	}

	ctx.globalToFree++;

	if(ctx.scoped) {
		ctx.scopeToFree++;
	}

	return [
		["setLine:ofList:to:", stackPtr(), "DATA", val],
		["changeVar:by:", "sp", -1]
	];
}

function freeStack(num) {
	return [
		["changeVar:by:", "sp", num]
		//["doRepeat", num, [["deleteLine:ofList:", "last", "Stack"]]],
	];
}

function freeLocals(ctx) {
	var numToFree = ctx.globalToFree;

	if(ctx.scoped) {
		numToFree = ctx.scopeToFree;
		ctx.scopeToFree = 0;
		ctx.scopedLocalDepth = 0;
	}

	return freeStack(numToFree);
}

function fetchByName(ctx, n, expectedType) {
	if(ctx.locals[n] !== undefined) {
		var stackPos = stackPosFromOffset(getOffset(ctx, n));
		var o = ["getLine:ofList:", stackPos, "DATA"];

		if(expectedType) {
			var actualType = ctx.localTypes[n];
			var actualReferenceCount = actualType.split('*').length - 1;
			var expectedReferenceCount = expectedType.split('*').length - 1;

			if(expectedReferenceCount == actualReferenceCount - 1) {
				// dereference
				return ["getLine:ofList:", o, "DATA"];
			} else if(actualReferenceCount == expectedReferenceCount + 1) {
				// addressOf
				return stackPos;
			}

			if(expectedReferenceCount != actualReferenceCount)
				console.log("WARNING: Expecting "+expectedReferenceCount+", actually" + actualReferenceCount);
		}

		return o;
	} else if(ctx.params.indexOf(n) > -1)
		return ["getParam", n.slice(1), "r"];
	else if( (n * 1) == n)
		return n
	else
		console.log("fetchByName undefined "+n);
		console.log(ctx.locals);
		return ["undefined"];
}

function addressOf(ctx, n, offset) {
	// TODO: full implementation
	// this will work for now, anyway

	// first, we need to get the address of the base pointer
	// this will either be a standard stack-based pointer, or a reference to rootGlobal

	var base = 0;

	if(ctx.rootGlobal[n.slice(1)])
		base = ctx.rootGlobal[n.slice(1)].ptr;
	else if(ctx.locals[n])
		base = ["getLine:ofList:", stackPosFromOffset(getOffset(ctx, n)), "DATA"];

	// then, we add the offset
	return ["+", base, offset];
}

function returnBlock(ctx, val) {
	var proc = freeLocals(ctx);

	if(ctx.gotoComplex) {
		proc = proc.concat(cleanGotoComplex());
	}

	if(val) {
		proc.push(["setVar:to:", "return value", formatValue(ctx, val[0], val[1])]);
	}

	proc.push(["stopScripts", "this script"]);

	return proc;
}

function callBlock(ctx, block) {
	var spec = block.funcName;
	var args = [];

	for(var a = 0; a < block.paramList.length; ++a) {
		args.push(formatValue(ctx, block.paramList[a][0], block.paramList[a][1]));
		spec += " "+specifierForType(block.paramList[a][0]);
	}

	return [
		["call", spec].concat(args)
	];
}

// TODO: more robust implementation to support heap

function dereferenceAndSet(ctx, ptr, content) {
	return [
		[
			"setLine:ofList:to:",
			stackPosFromOffset(getOffset(ctx, ptr)),
			"DATA",
			fetchByName(ctx, content)
		]
	];
}

function specForComparison(comp) {
	if(comp == "eq") {
		return "=";
	} else if(comp == "ne") {
		return "!=";
	} else if(comp == "slt" || comp == "ult") {
		return "<";
	} else if(comp == "sgt" || comp == "ugt") {
		return ">";
	}
	return "undefined";
}

function initGotoComplex() {
	return [
		["append:toList:", 0, "Label Stack"]
	];
}

function getCurrentLabel() {
	return ["getLine:ofList:", "last", "Label Stack"];
}

function cleanGotoComplex() {
	return [
		["deleteLine:ofList:", "last", "Label Stack"]
	];
}

function absoluteBranch(dest) {
	return [
		["setLine:ofList:to:", "last", "Label Stack", dest]
	];
}

function castToNumber(b) {
	return ["*", b, 1];
}

function icmpBlock(ctx, block) {
	var spec = specForComparison(block.val.operation);
	var negate = false;

	if(spec[0] == "!") {
		negate = true;
		spec = spec.slice(1);
	}

	var b = [spec, fetchByName(ctx, block.val.left), fetchByName(ctx, block.val.right)];

	if(negate) {
		b = ["not", b];
	}

	return castToNumber(b);
}

function signExtend(ctx, block) {
	// TODO: once we support typing correctly, sign extend will need a proper implementation too
	return fetchByName(ctx, block.source);
}
<commit_msg>srem in abckend<commit_after>// takes an IR function object and returns a list of Scratch blocks

module.exports.ffi = {};

module.exports.generateFunctionHat = function(functionContext, func) {
	var spec = func.funcName;
	var inputs = [];
	var defaults = [];

	functionContext.params = [];

	for(var i = 0; i < func.paramList.length; ++i) {
		var pName = "param" + i;

		if(func.paramList[i][1]) {
			pName = func.paramList[i][1].slice(1);
			inputs.push(pName);
			functionContext.params.push("%"+pName);
		} else {
			inputs.push(pName);
			functionContext.params.push(pName);
		}

		defaults.push(defaultForType(func.paramList[i][0]));
		spec += " "+specifierForType(func.paramList[i][0]);
	}

	return ["procDef", spec, inputs, defaults, false];

}

module.exports.compileFunction = function(func, IR) {
	var functionContext = {
		locals: {},
		localTypes: {},
		globalLocalDepth: 0,
		scopedLocalDepth: 0,
		params: [],
		gotoInit: false,
		globalToFree: 0,
		scopeToFree: 0,
		scoped: false,
		globals: IR.globals,
		rootGlobal: IR.rootGlobal
	}

	var blockList = [module.exports.generateFunctionHat(functionContext, func)];

	if(func.inGotoComplex) {
		blockList = blockList.concat(initGotoComplex());
	}

	for(var i = 0; i < func.code.length;) {
		var iGain = 1;

		var hasGotoComplex = functionContext.gotoComplex && functionContext.gotoComplex.okToUse && functionContext.gotoComplex.active; // this MUST be before compileInstruction for branching to work

		// optimize out alloca calls
		if(func.code[i].type == "set" && func.code[i].computation == [] && func.code[i].value == 0 &&
			func.code[i+1].type == "store" && func.code[i+1].destination.value == func.code[i].name) {

			func.code[i].value = func.code[i+1].src.value;
			iGain++;
		}

		// optimize out icmp in conditional branch
		if(func.code[i].type == "set" && func.code[i].val.type == "comparison" &&
			func.code[i+1].type == "branch" && func.code[i+1].conditional && func.code[i+1].condition == func.code[i].name) {

			func.code[i] = {
				type: "branch",
				conditional: true,
				dest: func.code[i+1].dest,
				falseDest: func.code[i+1].falseDest,
				condition: icmpBlock(functionContext, func.code[i]),
				rawCondition: true
			};

			iGain++;
		}

		var instruction = compileInstruction(functionContext, func.code[i]);

		if(!functionContext.gotoInit && functionContext.gotoComplex && functionContext.gotoComplex.okToUse) {
			blockList = blockList.concat([functionContext.gotoComplex.forever]);
			functionContext.gotoInit = true;
		}
		if(hasGotoComplex) {
			if(functionContext.gotoComplex.currentContext[2]) {
				functionContext.gotoComplex.currentContext[2] =
					functionContext.gotoComplex.currentContext[2].concat(instruction);
			} else {
				functionContext.gotoComplex.currentContext[2] = instruction;
			}
		} else {
			blockList = blockList.concat(instruction);
		}

		i += iGain;
	}

	//blockList = blockList.concat(returnBlock());

	return blockList;
}

function compileInstruction(ctx, block) {
	if(block.type == "call") {
		// calling a (potentially foreign) function
		return callBlock(ctx, block);
	} else if(block.type == "ffi") {
		// FFI block
		// load the code from the options
		return module.exports.ffi[block.ffiBlock];
	} else if(block.type == "set") {
		var val = 0;
		if(!block.val.vtype) console.log(block.val);
		var type = block.val.vtype || "";

		if(block.val.type == "return value") {
			val = ["readVariable", "return value"];
		} else if(block.val.type == "variable") {
			val = fetchByName(ctx, block.val.name, block.val.vtype);
			type = block.val.vtype;
		} else if(block.val.type == "arithmetic") {
			val = [block.val.operation, fetchByName(ctx, block.val.operand1), fetchByName(ctx, block.val.operand2)];
		} else if(block.val.type == "comparison") {
			val = icmpBlock(ctx, val);
		} else if(block.val.type == "sext") {
			val = signExtend(ctx, block.val);
		} else if(block.val.type == "addressOf") { // todo: full getelementptr implementation
			val = addressOf(ctx, block.val.base.name, block.val.offset);
		} else if(block.val.type == "srem") {
			val = ["computeFunction:of:", "floor", ["%", fetchByName(ctx, block.val.operand1), fetchByName(ctx, block.val.operand2)]]
		} else {
			console.log("Unknown equality in backend");
			console.log(block.val);
		}

		return compileInstruction(ctx, block.computation)
				.concat(allocateLocal(ctx, val, block.name, type));
	} else if(block.type == "ret") {
		return returnBlock(ctx, block.value);
	} else if(block.type == "store") {
		return dereferenceAndSet(ctx, block.destination.value, block.src.value);
	} else if(block.type == "gotoComplex") {
		ctx.gotoComplex = {
			context: [],
			okToUse: false,
			forever: ["doForever", []],
			active: true
		}

		//return [ctx.gotoComplex.forever];
	} else if(block.type == "label") {
		if(ctx.scoped) {
			ctx.gotoComplex.currentContext[2] =
				ctx.gotoComplex.currentContext[2].concat(freeLocals(ctx));
		}

		ctx.scoped = true;

		var chunk = ["doIfElse", ["=", getCurrentLabel(), block.label], [], []];

		ctx.gotoComplex.okToUse = true;
		ctx.gotoComplex.active = true;

		if(ctx.gotoComplex.currentContext) {
			ctx.gotoComplex.currentContext[3] = [chunk];
			ctx.gotoComplex.currentContext = ctx.gotoComplex.currentContext[3][0];
		} else {
			ctx.gotoComplex.currentContext = chunk;
			ctx.gotoComplex.context = ctx.gotoComplex.currentContext;
			ctx.gotoComplex.forever[1] = [ctx.gotoComplex.context];
		}

	} else if(block.type == "branch") {
		ctx.gotoComplex.active = false;

		if(block.conditional) {
			var cond = block.rawCondition ? block.condition : ["=", fetchByName(ctx, block.condition), 1];

			return [
				["doIfElse", cond, absoluteBranch(block.dest.slice(1)), absoluteBranch(block.falseDest.slice(1))]
			];
		} else {
			return absoluteBranch(block.dest);
		}
	}

	return [];
}

// fixme: stub
function defaultForType(type) {
	return 0;
}

// fixme: stub
function specifierForType(type) {
	return "%s";
}

// fixme: stub
function formatValue(ctx, type, value) {
	if(typeof value == "object") {
		if(value.type == "getelementptr") {
			// fixme: necessary and proper implementation
			return addressOf(ctx, value.base.val);
		}
	}

	if(value[0] == '%') {
		return fetchByName(ctx, value);
	}

	return value;
}

function getOffset(ctx, value) {
	return ctx.globalLocalDepth + ctx.scopedLocalDepth - ctx.locals[value];
}

function stackPtr() {
	return ["readVariable", "sp"];
}

function stackPosFromOffset(offset) {
	// optimize zero-index
	/*if(offset == 0) {
		return stackPtr();
	}*/

	return ["+", stackPtr(), offset];
}

// higher-level code generation

function allocateLocal(ctx, val, name, type) {
	if(name) {
		var depth = 0;

		if(ctx.scoped) {
			depth = ctx.globalLocalDepth + (ctx.scopedLocalDepth++);
		} else {
			depth = ctx.globalLocalDepth++;
		}

		ctx.locals[name] = depth;
		ctx.localTypes[name] = type;
	}

	ctx.globalToFree++;

	if(ctx.scoped) {
		ctx.scopeToFree++;
	}

	return [
		["setLine:ofList:to:", stackPtr(), "DATA", val],
		["changeVar:by:", "sp", -1]
	];
}

function freeStack(num) {
	return [
		["changeVar:by:", "sp", num]
		//["doRepeat", num, [["deleteLine:ofList:", "last", "Stack"]]],
	];
}

function freeLocals(ctx) {
	var numToFree = ctx.globalToFree;

	if(ctx.scoped) {
		numToFree = ctx.scopeToFree;
		ctx.scopeToFree = 0;
		ctx.scopedLocalDepth = 0;
	}

	return freeStack(numToFree);
}

function fetchByName(ctx, n, expectedType) {
	if(ctx.locals[n] !== undefined) {
		var stackPos = stackPosFromOffset(getOffset(ctx, n));
		var o = ["getLine:ofList:", stackPos, "DATA"];

		if(expectedType) {
			var actualType = ctx.localTypes[n];
			var actualReferenceCount = actualType.split('*').length - 1;
			var expectedReferenceCount = expectedType.split('*').length - 1;

			if(expectedReferenceCount == actualReferenceCount - 1) {
				// dereference
				return ["getLine:ofList:", o, "DATA"];
			} else if(actualReferenceCount == expectedReferenceCount + 1) {
				// addressOf
				return stackPos;
			}

			if(expectedReferenceCount != actualReferenceCount)
				console.log("WARNING: Expecting "+expectedReferenceCount+", actually" + actualReferenceCount);
		}

		return o;
	} else if(ctx.params.indexOf(n) > -1)
		return ["getParam", n.slice(1), "r"];
	else if( (n * 1) == n)
		return n
	else
		console.log("fetchByName undefined "+n);
		console.log(ctx.locals);
		return ["undefined"];
}

function addressOf(ctx, n, offset) {
	// TODO: full implementation
	// this will work for now, anyway

	// first, we need to get the address of the base pointer
	// this will either be a standard stack-based pointer, or a reference to rootGlobal

	var base = 0;

	if(ctx.rootGlobal[n.slice(1)])
		base = ctx.rootGlobal[n.slice(1)].ptr;
	else if(ctx.locals[n])
		base = ["getLine:ofList:", stackPosFromOffset(getOffset(ctx, n)), "DATA"];

	// then, we add the offset
	return ["+", base, offset];
}

function returnBlock(ctx, val) {
	var proc = freeLocals(ctx);

	if(ctx.gotoComplex) {
		proc = proc.concat(cleanGotoComplex());
	}

	if(val) {
		proc.push(["setVar:to:", "return value", formatValue(ctx, val[0], val[1])]);
	}

	proc.push(["stopScripts", "this script"]);

	return proc;
}

function callBlock(ctx, block) {
	var spec = block.funcName;
	var args = [];

	for(var a = 0; a < block.paramList.length; ++a) {
		args.push(formatValue(ctx, block.paramList[a][0], block.paramList[a][1]));
		spec += " "+specifierForType(block.paramList[a][0]);
	}

	return [
		["call", spec].concat(args)
	];
}

// TODO: more robust implementation to support heap

function dereferenceAndSet(ctx, ptr, content) {
	return [
		[
			"setLine:ofList:to:",
			stackPosFromOffset(getOffset(ctx, ptr)),
			"DATA",
			fetchByName(ctx, content)
		]
	];
}

function specForComparison(comp) {
	if(comp == "eq") {
		return "=";
	} else if(comp == "ne") {
		return "!=";
	} else if(comp == "slt" || comp == "ult") {
		return "<";
	} else if(comp == "sgt" || comp == "ugt") {
		return ">";
	}
	return "undefined";
}

function initGotoComplex() {
	return [
		["append:toList:", 0, "Label Stack"]
	];
}

function getCurrentLabel() {
	return ["getLine:ofList:", "last", "Label Stack"];
}

function cleanGotoComplex() {
	return [
		["deleteLine:ofList:", "last", "Label Stack"]
	];
}

function absoluteBranch(dest) {
	return [
		["setLine:ofList:to:", "last", "Label Stack", dest]
	];
}

function castToNumber(b) {
	return ["*", b, 1];
}

function icmpBlock(ctx, block) {
	var spec = specForComparison(block.val.operation);
	var negate = false;

	if(spec[0] == "!") {
		negate = true;
		spec = spec.slice(1);
	}

	var b = [spec, fetchByName(ctx, block.val.left), fetchByName(ctx, block.val.right)];

	if(negate) {
		b = ["not", b];
	}

	return castToNumber(b);
}

function signExtend(ctx, block) {
	// TODO: once we support typing correctly, sign extend will need a proper implementation too
	return fetchByName(ctx, block.source);
}
<|endoftext|>


<commit_before>
const csv = require("fast-csv");
const EventEmitter = require("events");

let devices = [];

const emitterInstance = new EventEmitter();

class Device {
	constructor(mac, macResolved){
		this.mac = mac;
		this.macResolved = macResolved;
		this.rssHistory = new Array();
		this.ssidHistory = new Array();
		emitterInstance.emit('newDevice', this);
	}
	updateRssStatistics(size, avg, variance, stdDeviation){
		this.rssStatistics = {
			size			: size,
			avg				: avg,
			variance		: variance,
			stdDeviation	: stdDeviation,
			time			: new Date(),
		};
	}
	toJSON() {
		let rssHistory = "";
		for (let rss in this.rssHistory){
			rssHistory += `"${rss}":"${rssHistory[rss]}",`
		}
		let ssidHistory = "";
		for (let ssid in this.ssidHistory){
			ssid += `"${ssid}":"${ssidHistory[rss]}",`
		}
		return `{
		"mac":"${this.mac}",
		"macResolved":"${this.macResolved}",
		"rssHistory":{${rssHistory}},
		"ssidHistory":{${ssidHistory}},
		"rssStatistics":${JSON.stringify(this.rssStatistics)}
		}`;
	}
}

function updedateDevices() {
	let mac = data[0];
	let rss = data[1];
	let ssid = data[2];
	let macResolved =	data[3];
	let curTime = new Date();
	if (! devices[mac]){
		devices[mac] = new Device(mac, macResolved);
	}
	devices[mac].rssHistory[curTime] = rss;
	devices[mac].ssidHistory[ssid] = curTime;
}

function getReport(){
	let rssDevices = [];
	let devicesCout = 0;
	for (let deviceKey in devices){
		let device = devices[deviceKey];
		let rssArray = [];
		let rssHistoryLength = 0;
		devicesCout++;
		for (let rssKey in device.rssHistory){
			let rss = device.rssHistory[rssKey];
			rssHistoryLength++;
			// get first integer from each Rss string on rssHistory
			let number = parseInt(rss.split(",")[0].split('-')[1]);
			// list of rss for a device
			rssArray.push(number);
			// list of rss from all devices
			rssDevices.push(number);
		}
		//statistics for a device
		let avg = arrayAvg(rssArray);
		let variance = arrayVariance(rssArray, avg);
		let std = Math.sqrt(variance);
		device.updateRssStatistics(rssHistoryLength, avg, variance, std);
	}
	//statistics for all devices
	let avgDevices = arrayAvg(rssDevices);
	let varianceDevices = arrayVariance(rssDevices, avgDevices);
	let std = Math.sqrt(varianceDevices);

	return {
		devicesCout					: devicesCout,
		overallAverage				: avgDevices,
		overallStandardDeviation	: std,
	}
}
function getDeviceReport(macAddress) {
	getReport();
	return devices[macAddress];
}
/*Math functions -------------------------------------------------------------*/
function arrayAvg(array){ //Avg function
	let sum = 0;
	for(let i = 0; i < array.length; i++) {
		sum += array[i];
	}
	return sum / array.length;
}

function arrayVariance(array, avg){//variance function
	let sum = 0;
	for(let i = 0; i < array.length; i++){
		sum += Math.pow(( array[i] - avg ), 2);
	}
	return sum / (array.length - 1);
}

/* ----------------------------------------------------------------------- */
// processs startup
function spawnTshark(){
	const execSync = require('child_process').execSync;
	const stdioConf = {stdio: ['ignore', 'pipe', 'ignore']};

	let iwConfList = execSync('iwconfig | grep wlan', stdioConf).toString().split('\n');
	let iwFaces = [];
	for (let iwFace of iwConfList){
		let iface = iwFace.split('\t')[0].split(' ')[0];
		if (iface.length > 1){
			try {
				execSync(`sudo ifconfig ${iface} down`, stdioConf);
				execSync(`sudo iwconfig ${iface} mode monitor`, stdioConf);
				execSync(`sudo ifconfig ${iface} up`, stdioConf);
			} catch (e) {
				console.error(`iface ${iface} will not be used`);
				continue;
			}
			iwFaces.push( iface );
		}
	}

	console.log(JSON.stringify( iwFaces ));

	if (iwFaces.length < 1){
		console.error('No suitable interface was found');
		throw new Error('No suitable interface was found');
	}

	let childIface = iwFaces[0];

	const spawn = require('child_process').spawn;
	const tsharkChild = spawn(
		'tshark', [
			'-I',
			'-i', childIface,
			'-T', 'fields',
			'-E', 'separator=,',
			'-E', 'quote=d',
			'-e', 'wlan.sa',
			'-e', 'radiotap.dbm_antsignal',
			'-e', 'wlan_mgt.ssid',
			'-e', 'wlan.sa_resolved',
			'-Y', 'wlan.sa'
		]);
	tsharkChild.stdout.setEncoding('utf8');
	return tsharkChild;
}

/* ----------------------------------------------------------------------- */

let csvStream = csv()
	.on("data", function(data){
		updedateDevices(data);
	})
	.on("end", function(){
		console.log("done with tshark");
	});

/* ----------------------------------------------------------------------- */

let tsharkChild;
function shutdown(){
	try {
		process.stdin.unpipe(csvStream);
		csvStream.end();
		tsharkChild.kill();
	} catch (e){
		console.error(e.message + 'while tshark.js shutdown');
	}
}

module.exports = () => {

	tsharkChild = spawnTshark();

	tsharkChild.stderr.on('data', (data) => {
		/* for future use */
		console.error(`stderr: ${data}`);
	});
	tsharkChild.on('close', (code) => {
		console.log(`child process exited with code ${code}`);
	});

	tsharkChild.stdout.pipe(csvStream);

	return {
		getDevices			: () => Array.from(devices),
		emitterInstance		: emitterInstance,
		shutdown			: shutdown,
		getReport			: getReport,
		getDeviceReport		: getDeviceReport,
	};
}
<commit_msg>[hotfix] Fail @ updedateDevices<commit_after>
const csv = require("fast-csv");
const EventEmitter = require("events");

let devices = [];

const emitterInstance = new EventEmitter();

class Device {
	constructor(mac, macResolved){
		this.mac = mac;
		this.macResolved = macResolved;
		this.rssHistory = new Array();
		this.ssidHistory = new Array();
		emitterInstance.emit('newDevice', this);
	}
	updateRssStatistics(size, avg, variance, stdDeviation){
		this.rssStatistics = {
			size			: size,
			avg				: avg,
			variance		: variance,
			stdDeviation	: stdDeviation,
			time			: new Date(),
		};
	}
	toJSON() {
		let rssHistory = "";
		for (let rss in this.rssHistory){
			rssHistory += `"${rss}":"${rssHistory[rss]}",`
		}
		let ssidHistory = "";
		for (let ssid in this.ssidHistory){
			ssid += `"${ssid}":"${ssidHistory[rss]}",`
		}
		return `{
		"mac":"${this.mac}",
		"macResolved":"${this.macResolved}",
		"rssHistory":{${rssHistory}},
		"ssidHistory":{${ssidHistory}},
		"rssStatistics":${JSON.stringify(this.rssStatistics)}
		}`;
	}
}

function updedateDevices(data) {
	let mac = data[0];
	let rss = data[1];
	let ssid = data[2];
	let macResolved =	data[3];
	let curTime = new Date();
	if (! devices[mac]){
		devices[mac] = new Device(mac, macResolved);
	}
	devices[mac].rssHistory[curTime] = rss;
	devices[mac].ssidHistory[ssid] = curTime;
}

function getReport(){
	let rssDevices = [];
	let devicesCout = 0;
	for (let deviceKey in devices){
		let device = devices[deviceKey];
		let rssArray = [];
		let rssHistoryLength = 0;
		devicesCout++;
		for (let rssKey in device.rssHistory){
			let rss = device.rssHistory[rssKey];
			rssHistoryLength++;
			// get first integer from each Rss string on rssHistory
			let number = parseInt(rss.split(",")[0].split('-')[1]);
			// list of rss for a device
			rssArray.push(number);
			// list of rss from all devices
			rssDevices.push(number);
		}
		//statistics for a device
		let avg = arrayAvg(rssArray);
		let variance = arrayVariance(rssArray, avg);
		let std = Math.sqrt(variance);
		device.updateRssStatistics(rssHistoryLength, avg, variance, std);
	}
	//statistics for all devices
	let avgDevices = arrayAvg(rssDevices);
	let varianceDevices = arrayVariance(rssDevices, avgDevices);
	let std = Math.sqrt(varianceDevices);

	return {
		devicesCout					: devicesCout,
		overallAverage				: avgDevices,
		overallStandardDeviation	: std,
	}
}
function getDeviceReport(macAddress) {
	getReport();
	return devices[macAddress];
}
/*Math functions -------------------------------------------------------------*/
function arrayAvg(array){ //Avg function
	let sum = 0;
	for(let i = 0; i < array.length; i++) {
		sum += array[i];
	}
	return sum / array.length;
}

function arrayVariance(array, avg){//variance function
	let sum = 0;
	for(let i = 0; i < array.length; i++){
		sum += Math.pow(( array[i] - avg ), 2);
	}
	return sum / (array.length - 1);
}

/* ----------------------------------------------------------------------- */
// processs startup
function spawnTshark(){
	const execSync = require('child_process').execSync;
	const stdioConf = {stdio: ['ignore', 'pipe', 'ignore']};

	let iwConfList = execSync('iwconfig | grep wlan', stdioConf).toString().split('\n');
	let iwFaces = [];
	for (let iwFace of iwConfList){
		let iface = iwFace.split('\t')[0].split(' ')[0];
		if (iface.length > 1){
			try {
				execSync(`sudo ifconfig ${iface} down`, stdioConf);
				execSync(`sudo iwconfig ${iface} mode monitor`, stdioConf);
				execSync(`sudo ifconfig ${iface} up`, stdioConf);
			} catch (e) {
				console.error(`iface ${iface} will not be used`);
				continue;
			}
			iwFaces.push( iface );
		}
	}

	console.log(JSON.stringify( iwFaces ));

	if (iwFaces.length < 1){
		console.error('No suitable interface was found');
		throw new Error('No suitable interface was found');
	}

	let childIface = iwFaces[0];

	const spawn = require('child_process').spawn;
	const tsharkChild = spawn(
		'tshark', [
			'-I',
			'-i', childIface,
			'-T', 'fields',
			'-E', 'separator=,',
			'-E', 'quote=d',
			'-e', 'wlan.sa',
			'-e', 'radiotap.dbm_antsignal',
			'-e', 'wlan_mgt.ssid',
			'-e', 'wlan.sa_resolved',
			'-Y', 'wlan.sa'
		]);
	tsharkChild.stdout.setEncoding('utf8');
	return tsharkChild;
}

/* ----------------------------------------------------------------------- */

let csvStream = csv()
	.on("data", function(data){
		updedateDevices(data);
	})
	.on("end", function(){
		console.log("done with tshark");
	});

/* ----------------------------------------------------------------------- */

let tsharkChild;
function shutdown(){
	try {
		process.stdin.unpipe(csvStream);
		csvStream.end();
		tsharkChild.kill();
	} catch (e){
		console.error(e.message + 'while tshark.js shutdown');
	}
}

module.exports = () => {

	tsharkChild = spawnTshark();

	tsharkChild.stderr.on('data', (data) => {
		/* for future use */
		console.error(`stderr: ${data}`);
	});
	tsharkChild.on('close', (code) => {
		console.log(`child process exited with code ${code}`);
	});

	tsharkChild.stdout.pipe(csvStream);

	return {
		getDevices			: () => Array.from(devices),
		emitterInstance		: emitterInstance,
		shutdown			: shutdown,
		getReport			: getReport,
		getDeviceReport		: getDeviceReport,
	};
}
<|endoftext|>


<commit_before>// Build User: User
// Version: 0.0.14
// Build Date: Mon May 28 2018 16:25:34 GMT-0400 (Eastern Daylight Time)

(function(root, factory) {
    'use strict';

    // Try to define a console object
    (function(){
        try {
            if (!console && ('undefined' !== typeof window)) {
                // Define the console if it does not exist
                if (!window.console) {
                    window.console = {};
                }

                // Union of Chrome, FF, IE, and Safari console methods
                var consoleFunctions = [
                    'log', 'info', 'warn', 'error', 'debug', 'trace', 'dir', 'group',
                    'groupCollapsed', 'groupEnd', 'time', 'timeEnd', 'profile', 'profileEnd',
                    'dirxml', 'assert', 'count', 'markTimeline', 'timeStamp', 'clear'
                ];
                // Define undefined methods as no-ops to prevent errors
                for (var i = 0; i < consoleFunctions.length; i++) {
                    if (!window.console[consoleFunctions[i]]) {
                        window.console[consoleFunctions[i]] = function() {};
                    }
                }
            }
        } catch(error) {
            // Not much to do if there is no console
        }

    })();

    // Determine the module system (if any)
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(factory);
    } else {
        // Node
        if (typeof exports !== 'undefined') {
            module.exports = factory();
        } else {
            // None
            root.SettingsManager = factory();
        }
    }

})(this, function() {
    'use strict';

    var _isFunction = function(func) {
        return !!func && 'function' === typeof func;
    },
    _merge = function(object1, object2) {
        // From (http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically)

        var array = Array.isArray(object2),
            dst = array && [] || {};
            // Merge algorithm adapted from:
            // (http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically)

        if (array) {
            object1 = object1 || [];
            dst = dst.concat(object1);
            object2.forEach(function(e, i) {
                if (typeof dst[i] === 'undefined') {
                    dst[i] = e;
                } else if (typeof e === 'object') {
                    dst[i] = _merge(object1[i], e);
                } else {
                    if (object1.indexOf(e) === -1) {
                        dst.push(e);
                    }
                }
            });
        } else {
            if (object1 && typeof object1 === 'object') {
                Object.keys(object1).forEach(function (key) {
                    dst[key] = object1[key];
                });
            }
            Object.keys(object2).forEach(function (key) {
                if (typeof object2[key] !== 'object' || !object2[key]) {
                    dst[key] = object2[key];
                }
                else {
                    if (!object1[key]) {
                        dst[key] = object2[key];
                    } else {
                        dst[key] = _merge(object1[key], object2[key]);
                    }
                }
            });
        }

        return dst;
    },
    InMemoryStore = function() {

        var _settings = {},
        _load = function(successCallback) {
            if (_isFunction(successCallback)) {
                setTimeout(function() {successCallback.call(null, _settings);}, 0);
            }
        },
        _save = function(settings, successCallback) {
            _settings = _merge(_settings, settings);

            if (_isFunction(successCallback)) {
                setTimeout(function() {successCallback.call(null);}, 0);
            }
        },
        _clear = function(successCallback) {
            _settings = {};

            if (_isFunction(successCallback)) {
                setTimeout(function() {successCallback.call(null);}, 0);
            }
        };

        return {
            load: _load,
            save: _save,
            clear: _clear
        };
    },
    SettingsManager = function(backingStore) {

        if (!(this instanceof SettingsManager)) {
            return new SettingsManager(backingStore);
        }

        var _backingStore = backingStore || new InMemoryStore(),
        _load = function(successCallback, errorCallback) {
            _backingStore.load(function(settings) {
                if (_isFunction(successCallback)) {
                    setTimeout(function() {successCallback.call(null, settings);}, 0);
                }
            }, errorCallback);
        },
        _save = function(settings, successCallback, errorCallback) {
            if (!settings) {
                if (_isFunction(successCallback)) {
                    setTimeout(function() {successCallback.call(null);}, 0);
                }
            } else if (settings && 'object' === typeof settings && !Array.isArray(settings)) {
                _backingStore.save(settings, successCallback, errorCallback);
            } else {
                if (_isFunction(errorCallback)) {
                    setTimeout(function() {errorCallback.call(null);}, 0);
                }
            }
        },
        _clear = function(successCallback, errorCallback) {
            _backingStore.clear(successCallback, errorCallback);
        };

        return {
            load: _load,
            save: _save,
            clear: _clear
        };

    };

    // Place the version as a member in the function
    SettingsManager.version = '0.0.14';

    return SettingsManager;

});<commit_msg>New dist<commit_after>// Build User: User
// Version: 0.0.14
// Build Date: Mon May 28 2018 16:26:59 GMT-0400 (Eastern Daylight Time)

(function(root, factory) {
    'use strict';

    // Try to define a console object
    (function(){
        try {
            if (!console && ('undefined' !== typeof window)) {
                // Define the console if it does not exist
                if (!window.console) {
                    window.console = {};
                }

                // Union of Chrome, FF, IE, and Safari console methods
                var consoleFunctions = [
                    'log', 'info', 'warn', 'error', 'debug', 'trace', 'dir', 'group',
                    'groupCollapsed', 'groupEnd', 'time', 'timeEnd', 'profile', 'profileEnd',
                    'dirxml', 'assert', 'count', 'markTimeline', 'timeStamp', 'clear'
                ];
                // Define undefined methods as no-ops to prevent errors
                for (var i = 0; i < consoleFunctions.length; i++) {
                    if (!window.console[consoleFunctions[i]]) {
                        window.console[consoleFunctions[i]] = function() {};
                    }
                }
            }
        } catch(error) {
            // Not much to do if there is no console
        }

    })();

    // Determine the module system (if any)
    if (typeof define === 'function' && define.amd) {
        // AMD
        define(factory);
    } else {
        // Node
        if (typeof exports !== 'undefined') {
            module.exports = factory();
        } else {
            // None
            root.SettingsManager = factory();
        }
    }

})(this, function() {
    'use strict';

    var _isFunction = function(func) {
        return !!func && 'function' === typeof func;
    },
    _merge = function(object1, object2) {
        // From (http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically)

        var array = Array.isArray(object2),
            dst = array && [] || {};
            // Merge algorithm adapted from:
            // (http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically)

        if (array) {
            object1 = object1 || [];
            dst = dst.concat(object1);
            object2.forEach(function(e, i) {
                if (typeof dst[i] === 'undefined') {
                    dst[i] = e;
                } else if (typeof e === 'object') {
                    dst[i] = _merge(object1[i], e);
                } else {
                    if (object1.indexOf(e) === -1) {
                        dst.push(e);
                    }
                }
            });
        } else {
            if (object1 && typeof object1 === 'object') {
                Object.keys(object1).forEach(function (key) {
                    dst[key] = object1[key];
                });
            }
            Object.keys(object2).forEach(function (key) {
                if (typeof object2[key] !== 'object' || !object2[key]) {
                    dst[key] = object2[key];
                }
                else {
                    if (!object1[key]) {
                        dst[key] = object2[key];
                    } else {
                        dst[key] = _merge(object1[key], object2[key]);
                    }
                }
            });
        }

        return dst;
    },
    InMemoryStore = function() {

        var _settings = {},
        _load = function(successCallback) {
            if (_isFunction(successCallback)) {
                setTimeout(function() {successCallback.call(null, _settings);}, 0);
            }
        },
        _save = function(settings, successCallback) {
            _settings = _merge(_settings, settings);

            if (_isFunction(successCallback)) {
                setTimeout(function() {successCallback.call(null);}, 0);
            }
        },
        _clear = function(successCallback) {
            _settings = {};

            if (_isFunction(successCallback)) {
                setTimeout(function() {successCallback.call(null);}, 0);
            }
        };

        return {
            load: _load,
            save: _save,
            clear: _clear
        };
    },
    SettingsManager = function(backingStore) {

        if (!(this instanceof SettingsManager)) {
            return new SettingsManager(backingStore);
        }

        var _backingStore = backingStore || new InMemoryStore(),
        _load = function(successCallback, errorCallback) {
            _backingStore.load(function(settings) {
                if (_isFunction(successCallback)) {
                    setTimeout(function() {successCallback.call(null, settings);}, 0);
                }
            }, errorCallback);
        },
        _save = function(settings, successCallback, errorCallback) {
            if (!settings) {
                if (_isFunction(successCallback)) {
                    setTimeout(function() {successCallback.call(null);}, 0);
                }
            } else if (settings && 'object' === typeof settings && !Array.isArray(settings)) {
                _backingStore.save(settings, successCallback, errorCallback);
            } else {
                if (_isFunction(errorCallback)) {
                    setTimeout(function() {errorCallback.call(null);}, 0);
                }
            }
        },
        _clear = function(successCallback, errorCallback) {
            _backingStore.clear(successCallback, errorCallback);
        };

        return {
            load: _load,
            save: _save,
            clear: _clear
        };

    };

    // Place the version as a member in the function
    SettingsManager.version = '0.0.14';

    return SettingsManager;

});<|endoftext|>


<commit_before>var $ = require('jquery');
var _ = require('underscore');
var cdb = require('cartodb-deep-insights.js');
var Polyglot = require('node-polyglot');
var ConfigModel = require('./data/config-model');
var EditorMapView = require('./editor/map/map-view');
var LayerDefinitionsCollection = require('./data/layer-definitions-collection');
var WidgetDefinitionsCollection = require('./data/widget-definitions-collection');
var VisDefinitionModel = require('./data/vis-definition-model');
var createEditorMenuTabPane = require('./components/tab-pane/create-editor-menu-tab-pane');
var EditorPaneTemplate = require('./editor-pane.tpl');
var EditorPaneIconItemTemplate = require('./editor-pane-icon.tpl');
var ModalsServiceModel = require('./components/modals/modals-service-model');
var viewFactory = require('./components/view-factory');

// JSON data passed from entry point (editor/visualizations/show.html):
var vizJSON = window.vizJSON;
var userData = window.userData;
var frontendConfig = window.frontendConfig;
var visualizationData = window.visualizationData;
var layersData = window.layersData;

// Setup root and top-level models and objects
var ACTIVE_LOCALE = 'en';
var Locale = require('../../locale/index');
var polyglot = new Polyglot({
  locale: ACTIVE_LOCALE, // Needed for pluralize behaviour
  phrases: Locale[ACTIVE_LOCALE]
});

window._t = polyglot.t.bind(polyglot);

// Setup and create the vis (map, layers etc.) + dashboard (widgets) from the given vizJSON
// Remove old zoom template in order to start using new component
// TODO: decide how to manage this problem
delete vizJSON.overlays[2].template; // Zoom template
var dashboard = cdb.deepInsights.createDashboard('#dashboard', vizJSON, {
  no_cdn: false,
  cartodb_logo: false,
  renderMenu: false
});

var mapId = visualizationData.map_id;
var layersCollection = dashboard.vis.map.layers;
var configModel = new ConfigModel(
  _.extend(
    {
      base_url: userData.base_url
    },
    frontendConfig
  )
);

var visDefinitionModel = new VisDefinitionModel(visualizationData, {
  configModel: configModel
});
var layerDefinitionsCollection = new LayerDefinitionsCollection(layersData, {
  configModel: configModel,
  mapId: mapId,
  layersCollection: layersCollection
});
var widgetDefinitionsCollection = new WidgetDefinitionsCollection([], {
  configModel: configModel,
  layersCollection: layersCollection,
  dashboardWidgets: dashboard.widgets,
  mapId: mapId
});

// TODO should we provide a dashboard.done of some sort?
dashboard.vis.done(function () {
  vizJSON.widgets.forEach(function (d) {
    widgetDefinitionsCollection.add(d);
  });
})
.error(function (err) {
  console.error(err);
});

var modals = new ModalsServiceModel();
var editorTabPaneView = createEditorMenuTabPane([
  {
    icon: 'edition',
    selected: true,
    createContentView: function () {
      return new EditorMapView({
        modals: modals,
        visDefinitionModel: visDefinitionModel,
        layerDefinitionsCollection: layerDefinitionsCollection,
        widgetDefinitionsCollection: widgetDefinitionsCollection
      });
    }
  }, {
    icon: 'view',
    createContentView: function () {
      return viewFactory.createByHTML('View');
    }
  }, {
    icon: 'odyssey',
    createContentView: function () {
      return viewFactory.createByHTML('Odyssey');
    }
  }, {
    icon: 'settings',
    createContentView: function () {
      return viewFactory.createByHTML('Settings');
    }
  }], {
    tabPaneOptions: {
      className: 'Editor-wrapper',
      template: EditorPaneTemplate,
      url: userData.base_url,
      avatar_url: userData.avatar_url,
      tabPaneItemOptions: {
        tagName: 'li',
        className: 'EditorMenu-navigationItem'
      }
    },
    tabPaneItemIconOptions: {
      tagName: 'button',
      template: EditorPaneIconItemTemplate,
      className: 'EditorMenu-navigationLink'
    }
  });

$('.js-editor').prepend(editorTabPaneView.render().$el);

// Expose the root stuff to be able to inspect and modify state from developer console
window.configModel = configModel;
window.dashboard = dashboard;
window.layerDefinitionsCollection = layerDefinitionsCollection;
window.modals = modals;
window.viewFactory = viewFactory;
window.vis = dashboard.vis;
window.visDefinitionModel = visDefinitionModel;
window.widgetDefinitionsCollection = widgetDefinitionsCollection;
<commit_msg>Center map properly when widgets are added<commit_after>var $ = require('jquery');
var _ = require('underscore');
var cdb = require('cartodb-deep-insights.js');
var Polyglot = require('node-polyglot');
var ConfigModel = require('./data/config-model');
var EditorMapView = require('./editor/map/map-view');
var LayerDefinitionsCollection = require('./data/layer-definitions-collection');
var WidgetDefinitionsCollection = require('./data/widget-definitions-collection');
var VisDefinitionModel = require('./data/vis-definition-model');
var createEditorMenuTabPane = require('./components/tab-pane/create-editor-menu-tab-pane');
var EditorPaneTemplate = require('./editor-pane.tpl');
var EditorPaneIconItemTemplate = require('./editor-pane-icon.tpl');
var ModalsServiceModel = require('./components/modals/modals-service-model');
var viewFactory = require('./components/view-factory');

// JSON data passed from entry point (editor/visualizations/show.html):
var vizJSON = window.vizJSON;
var userData = window.userData;
var frontendConfig = window.frontendConfig;
var visualizationData = window.visualizationData;
var layersData = window.layersData;

// Setup root and top-level models and objects
var ACTIVE_LOCALE = 'en';
var Locale = require('../../locale/index');
var polyglot = new Polyglot({
  locale: ACTIVE_LOCALE, // Needed for pluralize behaviour
  phrases: Locale[ACTIVE_LOCALE]
});

window._t = polyglot.t.bind(polyglot);

// Setup and create the vis (map, layers etc.) + dashboard (widgets) from the given vizJSON
// Remove old zoom template in order to start using new component
// TODO: decide how to manage this problem
delete vizJSON.overlays[2].template; // Zoom template
var dashboard = cdb.deepInsights.createDashboard('#dashboard', vizJSON, {
  no_cdn: false,
  cartodb_logo: false,
  renderMenu: false
});

var mapId = visualizationData.map_id;
var layersCollection = dashboard.vis.map.layers;
var configModel = new ConfigModel(
  _.extend(
    {
      base_url: userData.base_url
    },
    frontendConfig
  )
);

var visDefinitionModel = new VisDefinitionModel(visualizationData, {
  configModel: configModel
});
var layerDefinitionsCollection = new LayerDefinitionsCollection(layersData, {
  configModel: configModel,
  mapId: mapId,
  layersCollection: layersCollection
});
var widgetDefinitionsCollection = new WidgetDefinitionsCollection([], {
  configModel: configModel,
  layersCollection: layersCollection,
  dashboardWidgets: dashboard.widgets,
  mapId: mapId
});

// TODO should we provide a dashboard.done of some sort?
dashboard.vis.done(function () {
  vizJSON.widgets.forEach(function (d) {
    widgetDefinitionsCollection.add(d);
  });

  widgetDefinitionsCollection.bind('add remove reset', function () {
    dashboard.vis.mapView.invalidateSize();
  });

  dashboard.vis.centerMapToOrigin();
})
.error(function (err) {
  console.error(err);
});

var modals = new ModalsServiceModel();
var editorTabPaneView = createEditorMenuTabPane([
  {
    icon: 'edition',
    selected: true,
    createContentView: function () {
      return new EditorMapView({
        modals: modals,
        visDefinitionModel: visDefinitionModel,
        layerDefinitionsCollection: layerDefinitionsCollection,
        widgetDefinitionsCollection: widgetDefinitionsCollection
      });
    }
  }, {
    icon: 'view',
    createContentView: function () {
      return viewFactory.createByHTML('View');
    }
  }, {
    icon: 'odyssey',
    createContentView: function () {
      return viewFactory.createByHTML('Odyssey');
    }
  }, {
    icon: 'settings',
    createContentView: function () {
      return viewFactory.createByHTML('Settings');
    }
  }], {
    tabPaneOptions: {
      className: 'Editor-wrapper',
      template: EditorPaneTemplate,
      url: userData.base_url,
      avatar_url: userData.avatar_url,
      tabPaneItemOptions: {
        tagName: 'li',
        className: 'EditorMenu-navigationItem'
      }
    },
    tabPaneItemIconOptions: {
      tagName: 'button',
      template: EditorPaneIconItemTemplate,
      className: 'EditorMenu-navigationLink'
    }
  });

$('.js-editor').prepend(editorTabPaneView.render().$el);

// Expose the root stuff to be able to inspect and modify state from developer console
window.configModel = configModel;
window.dashboard = dashboard;
window.layerDefinitionsCollection = layerDefinitionsCollection;
window.modals = modals;
window.viewFactory = viewFactory;
window.vis = dashboard.vis;
window.visDefinitionModel = visDefinitionModel;
window.widgetDefinitionsCollection = widgetDefinitionsCollection;
<|endoftext|>


<commit_before>'use strict';

const EmberApp = require('ember-cli/lib/broccoli/ember-app');
const shim = require('@html-next/flexi-layouts/lib/pod-templates-shim');

shim(EmberApp);

module.exports = function(defaults) {
  const app = new EmberApp(defaults, {
    'asset-cache': {
      include: [
        'assets/**/*',
        'favicons/**/*',
        'fonts/**/*',
        'img/**/*',
        'svgs/**/*'
      ],
      version: '10'
    },
    'ember-service-worker': {
      versionStrategy: 'every-build'
    },
    'esw-cache-fallback': {
      patterns: [
        '/github-repos'
      ]
    },
    'esw-prember': {
      version: '10'
    },
    emberCliConcat: {
      js: {
        concat: process.env.EMBER_ENV === 'production',
        useAsync: process.env.EMBER_ENV === 'production'
      },
      css: {
        concat: false
      }
    },
    'ember-img-lazy': {
      lazyFastBoot: true,
      setSrcImmediately: true,
      observerConfig: {
        rootMargin: '100px 0px',
        threshold: 0.01
      }
    },
    'ember-prism': {
      'components': ['scss', 'javascript', 'handlebars', 'http', 'json'],
      'plugins': ['line-numbers', 'normalize-whitespace', 'show-language']
    },
    fingerprint: {
      extensions: ['js', 'css', 'map']
    },
    inlineContent: {
      app: 'inline/styles/app.css',
      blog: 'inline/styles/blog.css',
      contact: 'inline/styles/contact.css',
      'ember-consulting': 'inline/styles/ember-consulting.css',
      fonts: 'inline/styles/fonts.css',
      'google-analytics': {
        file: 'inline/ga.js',
        enabled: process.env.EMBER_ENV === 'production'
      },
      home: 'inline/styles/home.css',
      loadCSS: 'inline/loadCSS.js',
      'loading-indicator': {
        file: 'inline/loading-indicator.html',
        enabled: false
      },
      'open-source': 'inline/styles/open-source.css'
    },
    prember: {
      baseRoot: 'https://shipshape.io',
      urls: buildPremberUrls()
    },
    SRI: {
      enabled: false
    },
    treeShaking: {
      enabled: true,
      include: [
        'ember-validators/format.js',
        'ember-validators/presence.js'
      ]
    },
    vendorFiles: { 'jquery.js': null }
  });

  return app.toTree();
};

/**
 * Builds the prember urls for the blog and static pages
 * @returns {string[]} The urls for prember
 */
function buildPremberUrls() {
  // Build prember urls
  const urls = [
    '/',
    '/ember-consulting',
    '/open-source',
    '/contact',
    '/blog'
  ];

  const { extname } = require('path');
  const walkSync = require('walk-sync');

  const paths = walkSync('app/blog');

  const mdFiles = paths.filter(path => extname(path) === '.md')
    .map((path) => {
      const stripMD = path.replace(/\.md/, '');
      return `/blog/${stripMD}`;
    });

  mdFiles.forEach((file) => {
    urls.push(file);
  });

  return urls;
}
<commit_msg>Use registration inline for SW<commit_after>'use strict';

const EmberApp = require('ember-cli/lib/broccoli/ember-app');
const shim = require('@html-next/flexi-layouts/lib/pod-templates-shim');

shim(EmberApp);

module.exports = function(defaults) {
  const app = new EmberApp(defaults, {
    'asset-cache': {
      include: [
        'assets/**/*',
        'favicons/**/*',
        'fonts/**/*',
        'img/**/*',
        'svgs/**/*'
      ],
      version: '10'
    },
    'ember-service-worker': {
      registrationStrategy: 'inline',
      versionStrategy: 'every-build'
    },
    'esw-cache-fallback': {
      patterns: [
        '/github-repos'
      ]
    },
    'esw-prember': {
      version: '10'
    },
    emberCliConcat: {
      js: {
        concat: process.env.EMBER_ENV === 'production',
        useAsync: process.env.EMBER_ENV === 'production'
      },
      css: {
        concat: false
      }
    },
    'ember-img-lazy': {
      lazyFastBoot: true,
      setSrcImmediately: true,
      observerConfig: {
        rootMargin: '100px 0px',
        threshold: 0.01
      }
    },
    'ember-prism': {
      'components': ['scss', 'javascript', 'handlebars', 'http', 'json'],
      'plugins': ['line-numbers', 'normalize-whitespace', 'show-language']
    },
    fingerprint: {
      extensions: ['js', 'css', 'map']
    },
    inlineContent: {
      app: 'inline/styles/app.css',
      blog: 'inline/styles/blog.css',
      contact: 'inline/styles/contact.css',
      'ember-consulting': 'inline/styles/ember-consulting.css',
      fonts: 'inline/styles/fonts.css',
      'google-analytics': {
        file: 'inline/ga.js',
        enabled: process.env.EMBER_ENV === 'production'
      },
      home: 'inline/styles/home.css',
      loadCSS: 'inline/loadCSS.js',
      'loading-indicator': {
        file: 'inline/loading-indicator.html',
        enabled: false
      },
      'open-source': 'inline/styles/open-source.css'
    },
    prember: {
      baseRoot: 'https://shipshape.io',
      urls: buildPremberUrls()
    },
    SRI: {
      enabled: false
    },
    treeShaking: {
      enabled: true,
      include: [
        'ember-validators/format.js',
        'ember-validators/presence.js'
      ]
    },
    vendorFiles: { 'jquery.js': null }
  });

  return app.toTree();
};

/**
 * Builds the prember urls for the blog and static pages
 * @returns {string[]} The urls for prember
 */
function buildPremberUrls() {
  // Build prember urls
  const urls = [
    '/',
    '/ember-consulting',
    '/open-source',
    '/contact',
    '/blog'
  ];

  const { extname } = require('path');
  const walkSync = require('walk-sync');

  const paths = walkSync('app/blog');

  const mdFiles = paths.filter(path => extname(path) === '.md')
    .map((path) => {
      const stripMD = path.replace(/\.md/, '');
      return `/blog/${stripMD}`;
    });

  mdFiles.forEach((file) => {
    urls.push(file);
  });

  return urls;
}
<|endoftext|>


<commit_before>import {FefDebounceHelper} from '../classes/fef-debounce-helper';
import {FefResponsiveHelper} from '../classes/fef-responsive-helper';

const KEYCODES = {
    'escape': 27
};
const SUBNAV_CLASS = 'js-subnav-container',
    INNER_CONTAINER_CLASS = 'js-subnav-content',
    BUTTON_BACK_CLASS = 'js-subnav-button-back',
    BUTTON_FORWARD_CLASS = 'js-subnav-button-forward',
    BUTTON_ACTIVE_CLASS = 'subnav__pager--visible',
    MASK_LEFT_CLASS = 'js-subnav-mask-left',
    MASK_RIGHT_CLASS = 'js-subnav-mask-right',
    MASK_VISIBLE_CLASS = 'subnav__mask--visible',
    ITEM_ACTIVE_CLASS = 'js-active-subnav-item',
    ITEM_GROUP_CLASS = 'js-nav-group',
    ITEM_OPEN_GROUP_CLASS = 'js-nav-group-open',
    ITEM_GROUP_WRAPPER_CLASS = 'js-nav-group-wrapper',
    OUTSIDE_CLICK_LISTENER_NAME = 'click.nav-group',
    OUTSIDE_KEYPRESS_LISTENER_NAME = 'keydown.nav-group',
    DEBOUNCETIME = 10,
    THROTTLETIME = 100,
    RIGHT_OFFSET = 24,
    BUTTON_BACK_THRESHOLD = 2,
    INNER_CONTAINER_SCROLL_PADDING = 84,
    ITEM_GROUP_MARGIN = 16,
    DEFAULT_SCROLL_TIME = 200;

export function init() {
    $(`.${SUBNAV_CLASS}`).each((index, element) => {
        new FefSubnav($(element));
    });
}

/**
 * This component handles the behaviour of the subnav, such as paging buttons and paging
 * functionality of a horizontally scrollable items list as well as the masks for
 * overflowing navigation items and 3rd-level navigation.
 */
export class FefSubnav {

    /**
     * @param $element jQuery element
     */
    constructor($element) {
        this.itemLeftPositions = new Array();
        this.itemRightPositions = new Array();
        this.$element = $element;
        this.$innerContainer = $(`.${INNER_CONTAINER_CLASS}`, this.$element);
        this.$buttonBack = $(`.${BUTTON_BACK_CLASS}`, this.$element);
        this.$buttonForward = $(`.${BUTTON_FORWARD_CLASS}`, this.$element);
        this.$maskLeft = $(`.${MASK_LEFT_CLASS}`, this.$element);
        this.$maskRight = $(`.${MASK_RIGHT_CLASS}`, this.$element);

        this.init();
        this.initItemPositions();
        this.registerListeners();

        this.width = window.width;

        // Immediately centering the nav bar doesn't work correctly - move to bottom of stack for correct results.
        setTimeout(() => {
            this.centerActiveItem();
        }, 0);
    }

    init() {
        this.updateButtonStatus();
        this.updateMaskStatus();
    }

    initItemPositions() {
        this.$innerContainer.children().each( (index, element) => {
            this.itemLeftPositions.push($(element).position().left);
            this.itemRightPositions.push($(element).position().left + $(element).innerWidth());
        });
    }

    onResize() {
        if (this.width !== window.width) {
            this.init();
            this.closeAllSubNavs();
            this.width = window.width;
        }
    }

    registerListeners() {
        $(window).on('resize', FefDebounceHelper.debounce(() => this.onResize(), DEBOUNCETIME));
        this.$innerContainer.on('scroll', FefDebounceHelper.debounce(() => this.init(), DEBOUNCETIME));
        this.$innerContainer.on('scroll', FefDebounceHelper.throttle(() => this.closeAllSubNavs(), THROTTLETIME));
        this.$buttonBack.on('click', () => { this.pageBack(); });
        this.$buttonForward.on('click', () => { this.pageForward(); });

        this.$element.on('click', `.${ITEM_GROUP_CLASS}`, (e) => {
            if ($(e.target).parent().hasClass(ITEM_GROUP_CLASS)) {
                e.preventDefault();
                e.stopPropagation();
                this.toggleSubNav($(e.currentTarget));
            }
        });
    }

    closeAllSubNavs() {
        $(document).off(`${OUTSIDE_CLICK_LISTENER_NAME} ${OUTSIDE_KEYPRESS_LISTENER_NAME}`);
        let openNavs = this.$element.find(`.${ITEM_OPEN_GROUP_CLASS}`);

        openNavs.each((index, el) => this.closeSubNav($(el)));
    }

    /**
     * Close a subnav-group by fading it out and then setting the styles/classes etc.
     */
    closeSubNav($navItem) {
        let $wrapper = $navItem.find(`.${ITEM_GROUP_WRAPPER_CLASS}`);
        $navItem.find('.expand-icon').removeClass('expand-icon--open');

        this.$element.removeClass('subnav--open-3rd-level');
        $('body').removeClass('l-dimmed-background');

        $wrapper.animate({'opacity': 0}, 200, 'easeInOutCubic', () => {
            $navItem.removeClass(`${ITEM_OPEN_GROUP_CLASS} nav-group--open`);

            // reset previously applied styles
            $wrapper.css({'left': '', 'right': '', 'opacity': '', 'max-height': ''});
            $wrapper.find('.nav-group__list').width('');
        });
    }

    toggleSubNav($navItem) {
        let isClosing = $navItem.hasClass(ITEM_OPEN_GROUP_CLASS);

        this.closeAllSubNavs();

        if (isClosing) {
            $navItem.children('a').attr({'aria-expanded': false, 'aria-haspopup': false});
            return;
        }

        $navItem.children('a').attr({'aria-expanded': true, 'aria-haspopup': true});

        this.$element.addClass('subnav--open-3rd-level');

        if(FefResponsiveHelper.isSmartphone()) {
            $('body').addClass('l-dimmed-background');
        }

        // Listen to clicks outside of the element and Escape keypress --> close element
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            if (!$(e.target).hasClass('nav-item')) {
                this.closeAllSubNavs();
            }
        }).on(OUTSIDE_KEYPRESS_LISTENER_NAME, (e) => {
            if(e.keyCode === KEYCODES.escape) {
                this.closeAllSubNavs();
            }
        });

        $navItem.addClass(`${ITEM_OPEN_GROUP_CLASS} nav-group--open`);
        $navItem.find('.expand-icon').addClass('expand-icon--open');

        if (!FefResponsiveHelper.isSmartphone()) {
            this.positionAndStretchSubNavGroup($navItem);
        } else {
            // Mobile: don't let the nav-group be taller than the space under the masthead. Make it scrollable and set a max-height to guarantee it.
            $navItem.find(`.${ITEM_GROUP_WRAPPER_CLASS}`).css({'max-height': `calc(100vh - ${$('.js-affix').outerHeight(true)}px)`});
        }
    }

    positionAndStretchSubNavGroup($navItem) {
        const $list = $navItem.find('.nav-group__list'),
            $listWrapper = $navItem.find(`.${ITEM_GROUP_WRAPPER_CLASS}`),
            navItemOffset = Math.max(ITEM_GROUP_MARGIN, $navItem.offset().left), // compensate for the 16px negative margin on the NavGroup
            listWidth = Math.max($navItem.outerWidth(), 200, $list.outerWidth());

        // If the parental nav item is wider than the list, make them the same size (compensate for margin)
        if ($navItem.outerWidth() + ITEM_GROUP_MARGIN > $list.outerWidth()) {
            $list.width($navItem.outerWidth() + ITEM_GROUP_MARGIN);
        }

        // if there's not enough space on the right side, align with the right side of the window
        if (navItemOffset + listWidth >= $(window).outerWidth()) {
            $listWrapper.css({'left': '', 'right': 0});
        } else {
            $listWrapper.css({'left': navItemOffset, 'right': ''});
        }
    }

    updateButtonStatus() {
        if (FefResponsiveHelper.isDesktop() || FefResponsiveHelper.isDesktopWide()) {
            // show forward button if needed
            if (this.isAtScrollEnd()) {
                this.$buttonForward.removeClass(BUTTON_ACTIVE_CLASS);
            } else if (this.hasScrollableOverflow()) {
                this.$buttonForward.addClass(BUTTON_ACTIVE_CLASS);
            } else {
                this.$buttonForward.removeClass(BUTTON_ACTIVE_CLASS);
            }

            // show back button if needed
            if (this.hasScrollableOverflow() && this.$innerContainer.scrollLeft() > BUTTON_BACK_THRESHOLD) {
                this.$buttonBack.addClass(BUTTON_ACTIVE_CLASS);
            } else {
                this.$buttonBack.removeClass(BUTTON_ACTIVE_CLASS);
            }
        }
    }

    updateMaskStatus() {
        if (FefResponsiveHelper.isSmartphone() || FefResponsiveHelper.isTablet()) {
            // show right mask if needed
            if (this.isAtScrollEnd()) {
                this.$maskRight.removeClass(MASK_VISIBLE_CLASS);
            } else if (this.hasScrollableOverflow()) {
                this.$maskRight.addClass(MASK_VISIBLE_CLASS);
            } else {
                this.$maskRight.removeClass(MASK_VISIBLE_CLASS);
            }

            // show left mask if needed
            if (this.hasScrollableOverflow() && this.$innerContainer.scrollLeft() > BUTTON_BACK_THRESHOLD) {
                this.$maskLeft.addClass(MASK_VISIBLE_CLASS);
            } else {
                this.$maskLeft.removeClass(MASK_VISIBLE_CLASS);
            }
        }
    }

    pageForward() {
        let visibleAreaRightEdge = this.$innerContainer.scrollLeft() + this.$innerContainer.innerWidth(),
            nextItem = this.itemRightPositions.findIndex(rightEdge => rightEdge > visibleAreaRightEdge),
            newPosition = this.itemLeftPositions[nextItem] - INNER_CONTAINER_SCROLL_PADDING;

        this.scrollToPosition(newPosition);
    }

    pageBack() {
        let visibleAreaLeftEdge = this.$innerContainer.scrollLeft() + INNER_CONTAINER_SCROLL_PADDING,
            nextItem = this.itemRightPositions.findIndex(rightEdge => rightEdge > visibleAreaLeftEdge),
            newPosition = this.itemLeftPositions[nextItem] - this.$innerContainer.innerWidth() + INNER_CONTAINER_SCROLL_PADDING;

        this.scrollToPosition(newPosition);
    }

    centerActiveItem() {
        let $active = $(`.${ITEM_ACTIVE_CLASS}`, this.$element);

        if ($active.length !== 1) {
            return;
        }

        let containerLeftEdge = this.$innerContainer.offset().left,
            containerMiddle = containerLeftEdge + .5 * this.$innerContainer.outerWidth(),
            activeLeftEdge = $active.offset().left,
            activeMiddle = activeLeftEdge + .5 * $active.outerWidth(),
            diff = activeMiddle - containerMiddle,
            currentScroll = this.$innerContainer.scrollLeft(),
            newScrollPos = currentScroll + diff;

        this.scrollToPosition(newScrollPos);
    }

    scrollToPosition(position, time) {
        time = typeof time === 'undefined' ? DEFAULT_SCROLL_TIME : time;

        this.$innerContainer
            .stop(true, false)
            .animate( { scrollLeft: position }, time);
    }

    hasScrollableOverflow() {
        return this.$innerContainer[0].scrollWidth > this.$innerContainer.innerWidth() + RIGHT_OFFSET;
    }

    isAtScrollEnd() {
        return this.$innerContainer.scrollLeft() + this.$innerContainer.innerWidth() >= this.$innerContainer[0].scrollWidth;
    }
}
<commit_msg>SRFCMSAL-1753 - Attempt to keep subnav open as long as possible<commit_after>import {FefDebounceHelper} from '../classes/fef-debounce-helper';
import {FefResponsiveHelper} from '../classes/fef-responsive-helper';

const KEYCODES = {
    'escape': 27
};
const SUBNAV_CLASS = 'js-subnav-container',
    INNER_CONTAINER_CLASS = 'js-subnav-content',
    BUTTON_BACK_CLASS = 'js-subnav-button-back',
    BUTTON_FORWARD_CLASS = 'js-subnav-button-forward',
    BUTTON_ACTIVE_CLASS = 'subnav__pager--visible',
    MASK_LEFT_CLASS = 'js-subnav-mask-left',
    MASK_RIGHT_CLASS = 'js-subnav-mask-right',
    MASK_VISIBLE_CLASS = 'subnav__mask--visible',
    ITEM_ACTIVE_CLASS = 'js-active-subnav-item',
    ITEM_GROUP_CLASS = 'js-nav-group',
    ITEM_OPEN_GROUP_CLASS = 'js-nav-group-open',
    ITEM_GROUP_WRAPPER_CLASS = 'js-nav-group-wrapper',
    OUTSIDE_CLICK_LISTENER_NAME = 'click.nav-group',
    OUTSIDE_KEYPRESS_LISTENER_NAME = 'keydown.nav-group',
    DEBOUNCETIME = 10,
    THROTTLETIME = 100,
    RIGHT_OFFSET = 24,
    BUTTON_BACK_THRESHOLD = 2,
    INNER_CONTAINER_SCROLL_PADDING = 84,
    ITEM_GROUP_MARGIN = 16,
    DEFAULT_SCROLL_TIME = 200;

export function init() {
    $(`.${SUBNAV_CLASS}`).each((index, element) => {
        new FefSubnav($(element));
    });
}

/**
 * This component handles the behaviour of the subnav, such as paging buttons and paging
 * functionality of a horizontally scrollable items list as well as the masks for
 * overflowing navigation items and 3rd-level navigation.
 */
export class FefSubnav {

    /**
     * @param $element jQuery element
     */
    constructor($element) {
        this.itemLeftPositions = new Array();
        this.itemRightPositions = new Array();
        this.$element = $element;
        this.$innerContainer = $(`.${INNER_CONTAINER_CLASS}`, this.$element);
        this.$buttonBack = $(`.${BUTTON_BACK_CLASS}`, this.$element);
        this.$buttonForward = $(`.${BUTTON_FORWARD_CLASS}`, this.$element);
        this.$maskLeft = $(`.${MASK_LEFT_CLASS}`, this.$element);
        this.$maskRight = $(`.${MASK_RIGHT_CLASS}`, this.$element);

        this.init();
        this.initItemPositions();
        this.registerListeners();

        this.width = window.width;

        // Immediately centering the nav bar doesn't work correctly - move to bottom of stack for correct results.
        setTimeout(() => {
            this.centerActiveItem();
        }, 0);
    }

    init() {
        this.updateButtonStatus();
        this.updateMaskStatus();
    }

    initItemPositions() {
        this.$innerContainer.children().each( (index, element) => {
            this.itemLeftPositions.push($(element).position().left);
            this.itemRightPositions.push($(element).position().left + $(element).innerWidth());
        });
    }

    onResize() {
        if (this.isWidthChangedSignificant()) {
            this.init();
            this.closeAllSubNavs();
        }
        this.width = window.width;
    }

    // If changed more than 15%
    isWidthChangedSignificant() {
        if (this.width < window.width) {
            // More than 15% bigger than before
            return ((widow.width * 100 / this.width) - 100) > 15;
        } else {
            // More than 15% smaller than before
            return ((this.width * 100 / window.width) - 100) > 15;
        }
    }

    registerListeners() {
        $(window).on('resize', FefDebounceHelper.debounce(() => this.onResize(), DEBOUNCETIME));
        this.$innerContainer.on('scroll', FefDebounceHelper.debounce(() => this.init(), DEBOUNCETIME));
        this.$innerContainer.on('scroll', FefDebounceHelper.throttle(() => this.closeAllSubNavs(), THROTTLETIME));
        this.$buttonBack.on('click', () => { this.pageBack(); });
        this.$buttonForward.on('click', () => { this.pageForward(); });

        this.$element.on('click', `.${ITEM_GROUP_CLASS}`, (e) => {
            if ($(e.target).parent().hasClass(ITEM_GROUP_CLASS)) {
                e.preventDefault();
                e.stopPropagation();
                this.toggleSubNav($(e.currentTarget));
            }
        });
    }

    closeAllSubNavs() {
        $(document).off(`${OUTSIDE_CLICK_LISTENER_NAME} ${OUTSIDE_KEYPRESS_LISTENER_NAME}`);
        let openNavs = this.$element.find(`.${ITEM_OPEN_GROUP_CLASS}`);

        openNavs.each((index, el) => this.closeSubNav($(el)));
    }

    /**
     * Close a subnav-group by fading it out and then setting the styles/classes etc.
     */
    closeSubNav($navItem) {
        let $wrapper = $navItem.find(`.${ITEM_GROUP_WRAPPER_CLASS}`);
        $navItem.find('.expand-icon').removeClass('expand-icon--open');

        this.$element.removeClass('subnav--open-3rd-level');
        $('body').removeClass('l-dimmed-background');

        $wrapper.animate({'opacity': 0}, 200, 'easeInOutCubic', () => {
            $navItem.removeClass(`${ITEM_OPEN_GROUP_CLASS} nav-group--open`);

            // reset previously applied styles
            $wrapper.css({'left': '', 'right': '', 'opacity': '', 'max-height': ''});
            $wrapper.find('.nav-group__list').width('');
        });
    }

    toggleSubNav($navItem) {
        let isClosing = $navItem.hasClass(ITEM_OPEN_GROUP_CLASS);

        this.closeAllSubNavs();

        if (isClosing) {
            $navItem.children('a').attr({'aria-expanded': false, 'aria-haspopup': false});
            return;
        }

        $navItem.children('a').attr({'aria-expanded': true, 'aria-haspopup': true});

        this.$element.addClass('subnav--open-3rd-level');

        if(FefResponsiveHelper.isSmartphone()) {
            $('body').addClass('l-dimmed-background');
        }

        // Listen to clicks outside of the element and Escape keypress --> close element
        $(document).on(OUTSIDE_CLICK_LISTENER_NAME, (e) => {
            if (!$(e.target).hasClass('nav-item')) {
                this.closeAllSubNavs();
            }
        }).on(OUTSIDE_KEYPRESS_LISTENER_NAME, (e) => {
            if(e.keyCode === KEYCODES.escape) {
                this.closeAllSubNavs();
            }
        });

        $navItem.addClass(`${ITEM_OPEN_GROUP_CLASS} nav-group--open`);
        $navItem.find('.expand-icon').addClass('expand-icon--open');

        if (!FefResponsiveHelper.isSmartphone()) {
            this.positionAndStretchSubNavGroup($navItem);
        } else {
            // Mobile: don't let the nav-group be taller than the space under the masthead. Make it scrollable and set a max-height to guarantee it.
            $navItem.find(`.${ITEM_GROUP_WRAPPER_CLASS}`).css({'max-height': `calc(100vh - ${$('.js-affix').outerHeight(true)}px)`});
        }
    }

    positionAndStretchSubNavGroup($navItem) {
        const $list = $navItem.find('.nav-group__list'),
            $listWrapper = $navItem.find(`.${ITEM_GROUP_WRAPPER_CLASS}`),
            navItemOffset = Math.max(ITEM_GROUP_MARGIN, $navItem.offset().left), // compensate for the 16px negative margin on the NavGroup
            listWidth = Math.max($navItem.outerWidth(), 200, $list.outerWidth());

        // If the parental nav item is wider than the list, make them the same size (compensate for margin)
        if ($navItem.outerWidth() + ITEM_GROUP_MARGIN > $list.outerWidth()) {
            $list.width($navItem.outerWidth() + ITEM_GROUP_MARGIN);
        }

        // if there's not enough space on the right side, align with the right side of the window
        if (navItemOffset + listWidth >= $(window).outerWidth()) {
            $listWrapper.css({'left': '', 'right': 0});
        } else {
            $listWrapper.css({'left': navItemOffset, 'right': ''});
        }
    }

    updateButtonStatus() {
        if (FefResponsiveHelper.isDesktop() || FefResponsiveHelper.isDesktopWide()) {
            // show forward button if needed
            if (this.isAtScrollEnd()) {
                this.$buttonForward.removeClass(BUTTON_ACTIVE_CLASS);
            } else if (this.hasScrollableOverflow()) {
                this.$buttonForward.addClass(BUTTON_ACTIVE_CLASS);
            } else {
                this.$buttonForward.removeClass(BUTTON_ACTIVE_CLASS);
            }

            // show back button if needed
            if (this.hasScrollableOverflow() && this.$innerContainer.scrollLeft() > BUTTON_BACK_THRESHOLD) {
                this.$buttonBack.addClass(BUTTON_ACTIVE_CLASS);
            } else {
                this.$buttonBack.removeClass(BUTTON_ACTIVE_CLASS);
            }
        }
    }

    updateMaskStatus() {
        if (FefResponsiveHelper.isSmartphone() || FefResponsiveHelper.isTablet()) {
            // show right mask if needed
            if (this.isAtScrollEnd()) {
                this.$maskRight.removeClass(MASK_VISIBLE_CLASS);
            } else if (this.hasScrollableOverflow()) {
                this.$maskRight.addClass(MASK_VISIBLE_CLASS);
            } else {
                this.$maskRight.removeClass(MASK_VISIBLE_CLASS);
            }

            // show left mask if needed
            if (this.hasScrollableOverflow() && this.$innerContainer.scrollLeft() > BUTTON_BACK_THRESHOLD) {
                this.$maskLeft.addClass(MASK_VISIBLE_CLASS);
            } else {
                this.$maskLeft.removeClass(MASK_VISIBLE_CLASS);
            }
        }
    }

    pageForward() {
        let visibleAreaRightEdge = this.$innerContainer.scrollLeft() + this.$innerContainer.innerWidth(),
            nextItem = this.itemRightPositions.findIndex(rightEdge => rightEdge > visibleAreaRightEdge),
            newPosition = this.itemLeftPositions[nextItem] - INNER_CONTAINER_SCROLL_PADDING;

        this.scrollToPosition(newPosition);
    }

    pageBack() {
        let visibleAreaLeftEdge = this.$innerContainer.scrollLeft() + INNER_CONTAINER_SCROLL_PADDING,
            nextItem = this.itemRightPositions.findIndex(rightEdge => rightEdge > visibleAreaLeftEdge),
            newPosition = this.itemLeftPositions[nextItem] - this.$innerContainer.innerWidth() + INNER_CONTAINER_SCROLL_PADDING;

        this.scrollToPosition(newPosition);
    }

    centerActiveItem() {
        let $active = $(`.${ITEM_ACTIVE_CLASS}`, this.$element);

        if ($active.length !== 1) {
            return;
        }

        let containerLeftEdge = this.$innerContainer.offset().left,
            containerMiddle = containerLeftEdge + .5 * this.$innerContainer.outerWidth(),
            activeLeftEdge = $active.offset().left,
            activeMiddle = activeLeftEdge + .5 * $active.outerWidth(),
            diff = activeMiddle - containerMiddle,
            currentScroll = this.$innerContainer.scrollLeft(),
            newScrollPos = currentScroll + diff;

        this.scrollToPosition(newScrollPos);
    }

    scrollToPosition(position, time) {
        time = typeof time === 'undefined' ? DEFAULT_SCROLL_TIME : time;

        this.$innerContainer
            .stop(true, false)
            .animate( { scrollLeft: position }, time);
    }

    hasScrollableOverflow() {
        return this.$innerContainer[0].scrollWidth > this.$innerContainer.innerWidth() + RIGHT_OFFSET;
    }

    isAtScrollEnd() {
        return this.$innerContainer.scrollLeft() + this.$innerContainer.innerWidth() >= this.$innerContainer[0].scrollWidth;
    }
}
<|endoftext|>


<commit_before>define(['models/instance', 'rsvp', 'controllers/notifications', 'underscore',
'modal', 'controllers/notifications', 'react', 'components/common/glyphicon'],
function(Instance, RSVP, Notifications, _, Modal, Notifications, React,
Glyphicon) {

    /*
     * models.Identity identity
     * string machineId
     * string sizeId
     * string instanceName
     */
    var launchInstance = function(identity, machineId, sizeId, instanceName) {
        return new RSVP.Promise(function(resolve, reject) {

            if (!instanceName || instanceName.length === 0) {
                reject(["Instance name is required"]);
                return;
            }

            var instance = new Instance({identity: {
                id: identity.id,
                provider: identity.get('provider_id')
            }});

            var params = {
                machine_alias: machineId,
                size_alias: sizeId,
                name: instanceName
            };

            instance.save(params, {
                success: function(model) {
                    Notifications.success("Instance Launched", "Your instance will be ready soon.");
                    resolve(model);
                },
                error: function(model, response, options) {
                    if (response.status < 500) {
                        var response = JSON.parse(response.responseText);
                        var messages = _.pluck(response.errors, 'message');
                        reject(messages);
                    } else {
                        reject();
                    }
                }
            });
        });
    };

    var terminate = function(instance) {

        var header = "Are you sure you want to terminate this instance?";
        var body = React.DOM.div({},
            React.DOM.p({className: 'alert alert-error'},
                Glyphicon({name: 'warning-sign'}),
                React.DOM.strong({}, 'WARNING'),
                ' Unmount volumes within your instance ',
                'before terminating or risk corrupting your data and the volume'),
            React.DOM.p({},
                'Your instance ',
                React.DOM.strong({},
                    instance.get('name'),
                    ' #', instance.get('id')),
                ' will be shut down and all data will be permanently lost!'),
            React.DOM.p({},
                React.DOM.em({}, "Note:"),
                ' Your resource usage charts will not relect changes until the ',
                'instance is completely terminated and has disappeared ',
                'from your list of instances.'));

        Modal.alert(header, body, {
            onConfirm : function() {
                return new RSVP.Promise(function(resolve, reject) {
                    Notifications.info('Terminating Instance...', 'Please wait while your instance terminates.');

                    instance.destroy({wait: true, success: resolve, error: reject});
                });
            },
            okButtonText: 'Yes, terminate this instance'
        });
    };

    var stop = function(instance) {
        // If the instance is already starting/stopping inform user and return
        if (instance.get('state') === 'active - powering-off') {
            Notifications.warning('Stopping Instance', 'Please wait while your instance stops.');
            return;
        }

        var header = 'Stop Instance';
        var body = [
            React.DOM.p(""),
            React.DOM.p({}, React.DOM.strong({}, "NOTE:"), " This will NOT affect your resources. To preserve resources and time allocation you must suspend your instance.")
        ];
        var okButtonText = 'Stop Instance';
        var onConfirm = function() {
            return new RSVP.Promise(function(resolve, reject) {
                instance.stop({
                    success: function(model) {
                        Notifications.success('Stop Instance', 'Instance successfully stopped');
                        resolve(model);
                    },
                    error: function() {
                        Notifications.danger('Error', 'Could not stop instance');
                        reject(response);
                    }
                });
            });
        };

        Modal.alert(header, body, {
            onConfirm: onConfirm,
            okButtonText: okButtonText
        });
    };

    var canLaunchInstance = function(instance) {
        // TODO: this
        return true;
    };

    var start = function(instance) {
        if (instance.get('state') === 'shutoff - powering-on') {
            Notifications.warning('Starting Instance','Please wait while your instance starts. Refresh "My Instances" to check its status.');
            return;
        }

        // Make sure user has enough quota to resume this instance
        var header = 'Start Instance', body, okButtonText, onConfirm;
        if (canLaunchInstance(instance)) {
            body = [
                React.DOM.p({className: 'alert alert-warning'},
                    Glyphicon({name: 'warning-sign'}),
                    " ",
                    React.DOM.strong({}, 'WARNING'),
                    ' In order to start a stopped instance, you must have sufficient quota and the cloud must have enough room to support your instance\'s size.')
            ];
            okButtonText = 'Start Instance';
            onConfirm = function() {
                //Notifications.info('Starting Instance', 'Instance will be available momentarily.');

                // Prevent user from being able to quickly start multiple instances and go over quota
                instance.set({state: 'shutoff - powering-on'});

                return new RSVP.Promise(function(resolve, reject) {
                    instance.start({
                        success: function(model) {
                            // Merges models to those that are accurate based on server response
                            // Atmo.instances.update();
                            Notifications.success('Success', 'Instance successfully started');
                            resolve(model);
                        },
                        error: function(response, status, error) {
                            instance.set({state: 'shutoff'});
                            Notifications.danger('Error', 'Could not start instance');
                            reject(response);
                        }
                    });
                });
            };
        } else {
            body = React.DOM.p({className: 'alert alert-danger'},
                Glyphicon({name: 'ban-circle'}),
                " ",
                React.DOM.strong({}, "Cannot start Instance"),
                " You do not have enough resources to start this instance. You must terminate, suspend, or stop another running instance, or request more resources.");
            okButtonText = 'Ok';
        }

        Modal.alert(header, body, {
            onConfirm: onConfirm,
            okButtonText: okButtonText
        });
    };

    return {
        launch: launchInstance,
        terminate: terminate,
        stop: stop,
        start: start
    };

});
<commit_msg>Add suspend and resume methods to instance controller<commit_after>define(['models/instance', 'rsvp', 'controllers/notifications', 'underscore',
'modal', 'controllers/notifications', 'react', 'components/common/glyphicon'],
function(Instance, RSVP, Notifications, _, Modal, Notifications, React,
Glyphicon) {

    /*
     * models.Identity identity
     * string machineId
     * string sizeId
     * string instanceName
     */
    var launchInstance = function(identity, machineId, sizeId, instanceName) {
        return new RSVP.Promise(function(resolve, reject) {

            if (!instanceName || instanceName.length === 0) {
                reject(["Instance name is required"]);
                return;
            }

            var instance = new Instance({identity: {
                id: identity.id,
                provider: identity.get('provider_id')
            }});

            var params = {
                machine_alias: machineId,
                size_alias: sizeId,
                name: instanceName
            };

            instance.save(params, {
                success: function(model) {
                    Notifications.success("Instance Launched", "Your instance will be ready soon.");
                    resolve(model);
                },
                error: function(model, response, options) {
                    if (response.status < 500) {
                        var response = JSON.parse(response.responseText);
                        var messages = _.pluck(response.errors, 'message');
                        reject(messages);
                    } else {
                        reject();
                    }
                }
            });
        });
    };

    var terminate = function(instance) {

        var header = "Are you sure you want to terminate this instance?";
        var body = React.DOM.div({},
            React.DOM.p({className: 'alert alert-error'},
                Glyphicon({name: 'warning-sign'}),
                React.DOM.strong({}, 'WARNING'),
                ' Unmount volumes within your instance ',
                'before terminating or risk corrupting your data and the volume'),
            React.DOM.p({},
                'Your instance ',
                React.DOM.strong({},
                    instance.get('name'),
                    ' #', instance.get('id')),
                ' will be shut down and all data will be permanently lost!'),
            React.DOM.p({},
                React.DOM.em({}, "Note:"),
                ' Your resource usage charts will not relect changes until the ',
                'instance is completely terminated and has disappeared ',
                'from your list of instances.'));

        Modal.alert(header, body, {
            onConfirm : function() {
                return new RSVP.Promise(function(resolve, reject) {
                    Notifications.info('Terminating Instance...', 'Please wait while your instance terminates.');

                    instance.destroy({wait: true, success: resolve, error: reject});
                });
            },
            okButtonText: 'Yes, terminate this instance'
        });
    };

    var stop = function(instance) {
        // If the instance is already starting/stopping inform user and return
        if (instance.get('state') === 'active - powering-off') {
            Notifications.warning('Stopping Instance', 'Please wait while your instance stops.');
            return;
        }

        var header = 'Stop Instance';
        var body = [
            React.DOM.p(""),
            React.DOM.p({}, React.DOM.strong({}, "NOTE:"), " This will NOT affect your resources. To preserve resources and time allocation you must suspend your instance.")
        ];
        var okButtonText = 'Stop Instance';
        var onConfirm = function() {
            return new RSVP.Promise(function(resolve, reject) {
                instance.stop({
                    success: function(model) {
                        Notifications.success('Stop Instance', 'Instance successfully stopped');
                        resolve(model);
                    },
                    error: function(response) {
                        Notifications.danger('Error', 'Could not stop instance');
                        reject(response);
                    }
                });
            });
        };

        Modal.alert(header, body, {
            onConfirm: onConfirm,
            okButtonText: okButtonText
        });
    };

    var canLaunchInstance = function(instance) {
        // TODO: this
        return true;
    };

    var start = function(instance) {
        if (instance.get('state') === 'shutoff - powering-on') {
            Notifications.warning('Starting Instance','Please wait while your instance starts. Refresh "My Instances" to check its status.');
            return;
        }

        // Make sure user has enough quota to resume this instance
        var header = 'Start Instance', body, okButtonText, onConfirm;
        if (canLaunchInstance(instance)) {
            body = [
                React.DOM.p({className: 'alert alert-warning'},
                    Glyphicon({name: 'warning-sign'}),
                    " ",
                    React.DOM.strong({}, 'WARNING'),
                    ' In order to start a stopped instance, you must have sufficient quota and the cloud must have enough room to support your instance\'s size.')
            ];
            okButtonText = 'Start Instance';
            onConfirm = function() {
                //Notifications.info('Starting Instance', 'Instance will be available momentarily.');

                return new RSVP.Promise(function(resolve, reject) {
                    instance.start({
                        success: function(model) {
                            // Merges models to those that are accurate based on server response
                            // Atmo.instances.update();
                            Notifications.success('Success', 'Instance successfully started');
                            resolve(model);
                        },
                        error: function(response, status, error) {
                            Notifications.danger('Error', 'Could not start instance');
                            reject(response);
                        }
                    });
                });
            };
        } else {
            body = React.DOM.p({className: 'alert alert-danger'},
                Glyphicon({name: 'ban-circle'}),
                " ",
                React.DOM.strong({}, "Cannot start Instance"),
                " You do not have enough resources to start this instance. You must terminate, suspend, or stop another running instance, or request more resources.");
            okButtonText = 'Ok';
        }

        Modal.alert(header, body, {
            onConfirm: onConfirm,
            okButtonText: okButtonText
        });
    };

    var suspend = function(instance) {
        if (instance.get('state') === 'active - suspending') {
            Notifications.warning('Suspending Instance','Please wait while your instance suspend. Refresh "My Instances" to check its status.');
            return;
        }

        var header = 'Suspend Instance';
        var body = [
            React.DOM.p({className: 'alert alert-warning'},
                Glyphicon({name: 'warning-sign'}),
                " ",
                React.DOM.strong({}, "WARNING"),
                " Suspending an instance will freeze its state, and the IP address may change when you resume the instance."),
            React.DOM.p({},
                'Suspending an instance frees up resources for other users and allows you to safely preserve the state of your instance without imaging. ',
                'Your time allocation no longer counts against you in the suspended mode.'),
            React.DOM.p({},
                'Your resource usage charts will only reflect the freed resources once the instance\'s state is "suspended."')
        ];

        var okButtonText = 'Suspend Instance';
        var onConfirm = function() {
            return new RSVP.Promise(function(resolve, reject) {
                instance.suspend({
                    success: function(model) {
                        //Atmo.instances.update();
                        Notifications.success("Success", "Your instance is now suspended");
                        resolve(model);
                    },
                    error: function(response) {
                        Notifications.error("Error", "You instance could not be suspended");
                        reject(response);
                    }
                });
            });
        };

        Modal.alert(header, body, {
            okButtonText: okButtonText,
            onConfirm: onConfirm
        });
    };

    var resume = function(instance) {
        if (instance.get('state') == 'suspended - resuming') {
            Notifications.warning('Resuming Instance', 'Please wait while your instance resumes. Refresh "My Instances" to check its status.');
            return;
        }

        var header = 'Resume Instance', body, okButtonText, onConfirm;

        // Make sure user has enough quota to resume this instance
        if (canLaunchInstance(instance)) {
            okButtonText = 'Resume Instance';
            body = 'Your instance\'s IP address may change once it resumes.';
            onConfirm = function() {
                return new RSVP.Promise(function(resolve, reject) {
                    instance.resume({
                        success: function(model) {
                            //Atmo.instances.update();
                            Notifications.success('Success', 'Your instance is resuming');
                            resolve(model);
                        },
                        error: function(response) {
                          Notifications.danger('Error','You could not resume your instance');
                          reject(response);
                        }
                    });
                });
            };
        } else {
            body = React.DOM.p({className: 'alert alert-error'},
                Glyphicon({name: 'ban-circle'}),
                " ",
                React.DOM.strong({}, "Cannot resume instance"),
                " You do not have enough resources to resume this instance. You must terminate, suspend, or stop another running instance, or request more resources.");
            okButtonText = 'Ok';
        }

        Modal.alert(header, body, {
            okButtonText: okButtonText,
            onConfirm: onConfirm
        });
    };

    return {
        launch: launchInstance,
        terminate: terminate,
        stop: stop,
        start: start,
        suspend: suspend,
        resume: resume
    };

});
<|endoftext|>