<commit_before>"use strict";

/**************************
 * Main object
 **************************/
var Ned = {
	create(svg) {
		var editor = Object.create(Ned);
		editor.init(svg);

		return editor;
	},

	init(svg) {
		this.svg = null;

		this.snapping = 0;

		this.singleInputs = false;
		this.singleOutputs = false;

		this.dragConn = null;

		if (!(svg instanceof SVGElement)) {
			svg = document.querySelector(svg);
		}
		this.svg = svg;
		this.svg.ns = this.svg.namespaceURI;

		// group for all node elements
		this.nodegroup = document.createElementNS(this.svg.ns, "g");
		this.nodegroup.setAttribute("class", "Nodegroup");
		this.svg.appendChild(this.nodegroup);

		// group to draw the paths above the nodes
		this.pathGroup = document.createElementNS(this.svg.ns, "g");
		this.pathGroup.setAttribute("class", "PathGroup");
		this.svg.appendChild(this.pathGroup);
	},

	createNode(title) {
		var n = Object.create(Ned.Node);
		n.init (this, title);

		return n;
	},

	screenToWorld(pos) {
		return pos;
	}
};

/**************************
 * Node Object
 **************************/
Ned.Node = {
	init(ned, sTitle) {
		this.editor = ned;
		this.inputs = [];
		this.outputs = [];

		// ****************** root ******************
		this.eRoot = document.createElementNS(ned.svg.ns, "svg");
		this.eRoot.setAttribute("class", "NodeContainer");
		this.eRoot.setAttribute("overflow", "visible");
		ned.nodegroup.appendChild(this.eRoot);
		this.width = 200;
		this.height = 100;

		var headerSize = 24;

		// ****************** background ******************
		this.eBack = document.createElementNS(ned.svg.ns, "rect");
		this.eBack.setAttribute("class", "Background");
		this.eBack.setAttribute("width", "100%");
		this.eBack.setAttribute("height", "100%");
		this.eRoot.appendChild(this.eBack);

		// ****************** header ******************
		this.eHeader = document.createElementNS(ned.svg.ns, "g");
		this.eHeader.setAttribute("class", "Header");
		this.eRoot.appendChild(this.eHeader);
		this.eHeader.addEventListener("mousedown", (e) => {	this.beginNodeDrag (e);	});

		this.eHeaderBack = document.createElementNS(ned.svg.ns, "rect");
		this.eHeaderBack.setAttribute("width", "100%");
		this.eHeaderBack.setAttribute("height", headerSize);
		this.eHeader.appendChild(this.eHeaderBack);

		this.eHeaderText = document.createElementNS(ned.svg.ns, "text");
		this.eHeaderText.appendChild(document.createTextNode(sTitle));
		this.eHeaderText.setAttribute("x", "50%");
		this.eHeaderText.setAttribute("y", headerSize - 4); // padding to the bottom text
		this.eHeader.appendChild(this.eHeaderText);

		// ****************** foreign objects ******************
		this.eForeign = document.createElementNS(ned.svg.ns, "foreignObject");
		this.eForeign.setAttribute("x", "16");
		this.eForeign.setAttribute("y", headerSize);
		this.eRoot.appendChild(this.eForeign);

		// ****************** inputs and outputs ******************
		this.eInputs = document.createElementNS(ned.svg.ns, "svg");
		this.eInputs.setAttribute("class", "Inputs");
		this.eInputs.setAttribute("overflow", "visible");
		this.eInputs.setAttribute("x", "0%");
		this.eInputs.setAttribute("y", headerSize);
		this.eRoot.appendChild(this.eInputs);

		this.eOutputs = document.createElementNS(ned.svg.ns, "svg");
		this.eOutputs.setAttribute("class", "Outputs");
		this.eOutputs.setAttribute("overflow", "visible");
		this.eOutputs.setAttribute("x", "100%");
		this.eOutputs.setAttribute("y", headerSize);
		this.eRoot.appendChild(this.eOutputs);

		// ****************** foreign object test ******************
		this.eSelect = document.createElement("select");
		this.eForeign.appendChild(this.eSelect);

		for (let i = 0; i < 5; ++i)
		{
			var eOption = document.createElement("option");
			eOption.text = "test " + i;
			this.eSelect.appendChild(eOption);
		}
	},

	addInput(name) {
		var o = Object.create (Ned.Connector);
		this.inputs.push(o);
		o.init(this, name, true);
		return o;
	},

	addOutput(name) {
		var o = Object.create (Ned.Connector);
		this.outputs.push(o);
		o.init(this, name, false);
		return o;
	},

	updatePaths() {
		for (let c of this.inputs) c.updatePaths();
		for (let c of this.outputs) c.updatePaths();
	},

	get position() {
		return {
			x: this.eRoot.getAttribute("x"),
			y: this.eRoot.getAttribute("y")
		};
	},
	set position(pos) {
		if (this.editor.snapping) {
			var snp = this.editor.snapping;
			pos.x = Math.round(pos.x / snp) * snp;
			pos.y = Math.round(pos.y / snp) * snp;
		}

		this.eRoot.setAttribute("x", pos.x);
		this.eRoot.setAttribute("y", pos.y);
		this.updatePaths();
	},

	get width() {
		return this.eRoot.getAttribute("width");
	},
	set width(v) {
		this.eRoot.setAttribute("width", v);
	},

	get height() {
		return this.eRoot.getAttribute("height");
	},
	set height(v) {
		this.eRoot.setAttribute("height", v);
	},

	toTop() {
		this.editor.nodegroup.removeChild(this.eRoot);
		this.editor.nodegroup.appendChild(this.eRoot);
	},

	beginNodeDrag(e) {
		// we can only drag when the left mouse button is pressed
		if (e.button != 0) {
			return;
		}

		e.stopPropagation();

		this.toTop();
		var pos = this.position;
		var screenPos = this.editor.screenToWorld({ x: e.pageX, y: e.pageY });
		var offsetX = pos.x - screenPos.x;
		var offsetY = pos.y - screenPos.y;

		var onNodeDragMouseMove = (e) => {
			e.stopPropagation();
			e.preventDefault();
			var screenPos = this.editor.screenToWorld({ x: e.pageX, y: e.pageY });
			this.position = { x: screenPos.x + offsetX, y: screenPos.y + offsetY };
		};

		var onNodeDragMouseUp = (e) => {
			e.stopPropagation();
			e.preventDefault();

			window.removeEventListener("mousemove", onNodeDragMouseMove);
			window.removeEventListener("mouseup",  onNodeDragMouseUp);
		};

		window.addEventListener("mousemove", onNodeDragMouseMove);
		window.addEventListener("mouseup", onNodeDragMouseUp);
	}
};

/**************************
 * Node Connector Object
 **************************/
Ned.Connector = {
	init(node, name, isInput) {
		this.node = node;
		this.isInput = isInput;
		this.paths = [];

		var neRoot = isInput ? node.eInputs : node.eOutputs;

		// ****************** root ******************
		this.eRoot = document.createElementNS(this.editor.svg.ns, "svg");
		this.eRoot.setAttribute("class", isInput ? "Input" : "Output");
		this.eRoot.setAttribute("overflow", "visible");
		neRoot.appendChild(this.eRoot);

		// ****************** text ******************
		this.eText = document.createElementNS(this.editor.svg.ns, "text");
		this.eText.appendChild(document.createTextNode(name));
		this.eRoot.appendChild(this.eText);

		// ****************** dot ******************
		this.eDot = document.createElementNS(this.editor.svg.ns, "circle");
		this.eDot.ref = this; // we set the refference on the object we want to be able to connect to
		this.eDot.addEventListener("mousedown", (e) => { this.beginConnDrag(e); });
		this.eDot.addEventListener("mouseenter", (e) => { this.addHoverClass(); });
		this.eDot.addEventListener("mouseleave", (e) => { this.removeHoverClass(); });
		this.eRoot.appendChild(this.eDot);

		this.updatePosition();
	},

	addHoverClass() {
		var conn = this.editor.dragConn;
		if (!conn || (this.isInput === conn.isInput)) return;

		// we only add the class if we are dragging and the dragging connector isn't the same type of this connector
		this.eDot.setAttribute("class", "ConnHover");
	},
	removeHoverClass() {
		// on mouse leave we'll always remove the class
		this.eDot.removeAttribute("class");
	},

	updatePosition() {
		var rect = this.eRoot.getBoundingClientRect();

		var i = this.index;
		var y = (rect.height) + (rect.height * 1.5 * i)
		this.eRoot.setAttribute("y", y);
	},

	updatePaths() {
		for(let p of this.paths) p.update();
	},
	addPath(path) {
		if (this.hasSinglePath) {
			// destroy all current paths
			for(let p of this.paths) p.destroy();

			// clear the array
			this.paths = [];
		}

		this.paths.push(path);
	},
	removePath(path) {
		var index = this.paths.indexOf(path);
		if(index != -1) {
			this.paths.splice(index, 1);
		}
	},

	get editor() {
		return this.node.editor;
	},

	get index() {
		var list = (this.isInput) ? this.node.inputs : this.node.outputs;
		return list.indexOf(this);
	},

	get hasSinglePath() {
		return (this.isInput) ? this.editor.singleInputs : this.editor.singleOutputs;
	},
	get position() {
		var rect = this.eDot.getBoundingClientRect();
		// we need to keep the svg position in mind
		// and center it
		var screenPos = {
			x: rect.left + (rect.width / 2),
			y: rect.top + (rect.height / 2)
		};
		return this.editor.screenToWorld(screenPos);
	},

	// path parameter is optional and a path is created if none is supplied
	connectTo(conn, path) {
		if (this.isInput === conn.isInput) {
			//can't connect 2 inputs or 2 outputs
			if (path) path.destroy();
			return false;
		}

		if (!path) {
			var path = Object.create(Ned.Path);
			path.init(this);
		}
		path.setFinalConn(conn);

		// check if we don't already have a path like this
		if (this.paths.find((p) => path.equals(p))) {
			// this path already exists so delete it
			path.destroy();
			return false;
		}

		// remember the new path
		//add path to conn paths list to update it
		this.addPath(path);
		conn.addPath(path);
		return true;
	},

	beginConnDrag(e) {
		// we can only drag when the left mouse button is pressed
		if (e.button != 0) {
			return;
		}

		e.stopPropagation();
		e.preventDefault();

		this.editor.dragConn = this;
		var path = Object.create(Ned.Path);
		path.init(this);

		var onConnDragMouseMove = (e) => {
			e.stopPropagation();
			e.preventDefault();
			var mouseOffset = 3; // this offset is so the path isn't under the mouse when we release it. Else it isn't possible to get the underlying connector
			var screenPos = this.editor.screenToWorld({ x: e.pageX, y: e.pageY });
			var pos = {	x: screenPos.x, y: screenPos.y + mouseOffset };
			path.updateWithPos(pos);
		}

		var onConnDragMouseUp = (e) => {
			e.stopPropagation();
			e.preventDefault();

			this.editor.dragConn = null;
			var conn = e.target.ref; // get the saved refence to connect
			if (Ned.Connector.isPrototypeOf(conn)) {
				this.connectTo(conn, path);
				conn.removeHoverClass(); // we've stopped dragging so we'll make sure the hover class is removed
			} else {
				path.destroy();
			}

			window.removeEventListener("mousemove", onConnDragMouseMove);
			window.removeEventListener("mouseup", onConnDragMouseUp);
		};

		window.addEventListener("mousemove", onConnDragMouseMove);
		window.addEventListener("mouseup", onConnDragMouseUp);
	}
};

/**************************
 * Node path Object
 **************************/
Ned.Path = {
	init(conn) {
		this.editor = conn.editor;
		this.input = conn.isInput ? conn : null;
		this.output = conn.isInput ? null : conn;

		this.ePath = document.createElementNS(this.editor.svg.ns, "path");
		this.ePath.setAttribute("class", "Path");
		this.ePath.addEventListener("click", (e) => { this.onClicked(e); });

		this.editor.pathGroup.appendChild(this.ePath);

		var pos = conn.position;
		this.updateWithPos(pos);
	},

	destroy() {
		if (this.input) this.input.removePath(this);
		if (this.output) this.output.removePath(this);
		this.editor.pathGroup.removeChild(this.ePath);
		this.ePath = null;
	},

	setFinalConn(conn) {
		// if the input is set then set the output and vice versa
		if (this.input) this.output = conn;
		else this.input = conn;

		this.update();
	},

	equals(p) {
		return this.input === p.input && this.output === p.output;
	},

	onClicked(e) {
		// we can only drag when the left mouse button is pressed
		if (e.button != 0) {
			return;
		}

		this.destroy();
	},

	update() {
		this.updateWithPos ((this.input || this.output).position);
	},
	updateWithPos(pos) {
		var pos1 = this.output ? this.output.position : pos;
		var pos2 = this.input ? this.input.position : pos;
		this.setCurve (pos1.x, pos1.y, pos2.x, pos2.y);
	},

	setCurve(x1, y1, x2, y2) {
		var dif = Math.abs(x1 - x2) / 1.5,
			str = "M" + x1 + "," + y1 + " C" +	//MoveTo
				(x1 + dif) + "," + y1 + " " +	//First Control Point
				(x2 - dif) + "," + y2 + " " +	//Second Control Point
				(x2) + "," + y2;				//End Point

		this.ePath.setAttribute('d', str);
	}
}
<commit_msg>added foreign location todo<commit_after>"use strict";

/**************************
 * Main object
 **************************/
var Ned = {
	create(svg) {
		var editor = Object.create(Ned);
		editor.init(svg);

		return editor;
	},

	init(svg) {
		this.svg = null;

		this.snapping = 0;

		this.singleInputs = false;
		this.singleOutputs = false;

		this.dragConn = null;

		if (!(svg instanceof SVGElement)) {
			svg = document.querySelector(svg);
		}
		this.svg = svg;
		this.svg.ns = this.svg.namespaceURI;

		// group for all node elements
		this.nodegroup = document.createElementNS(this.svg.ns, "g");
		this.nodegroup.setAttribute("class", "Nodegroup");
		this.svg.appendChild(this.nodegroup);

		// group to draw the paths above the nodes
		this.pathGroup = document.createElementNS(this.svg.ns, "g");
		this.pathGroup.setAttribute("class", "PathGroup");
		this.svg.appendChild(this.pathGroup);
	},

	createNode(title) {
		var n = Object.create(Ned.Node);
		n.init (this, title);

		return n;
	},

	screenToWorld(pos) {
		return pos;
	}
};

/**************************
 * Node Object
 **************************/
Ned.Node = {
	init(ned, sTitle) {
		this.editor = ned;
		this.inputs = [];
		this.outputs = [];

		// ****************** root ******************
		this.eRoot = document.createElementNS(ned.svg.ns, "svg");
		this.eRoot.setAttribute("class", "NodeContainer");
		this.eRoot.setAttribute("overflow", "visible");
		ned.nodegroup.appendChild(this.eRoot);
		this.width = 200;
		this.height = 100;

		var headerSize = 24;

		// ****************** background ******************
		this.eBack = document.createElementNS(ned.svg.ns, "rect");
		this.eBack.setAttribute("class", "Background");
		this.eBack.setAttribute("width", "100%");
		this.eBack.setAttribute("height", "100%");
		this.eRoot.appendChild(this.eBack);

		// ****************** header ******************
		this.eHeader = document.createElementNS(ned.svg.ns, "g");
		this.eHeader.setAttribute("class", "Header");
		this.eRoot.appendChild(this.eHeader);
		this.eHeader.addEventListener("mousedown", (e) => {	this.beginNodeDrag (e);	});

		this.eHeaderBack = document.createElementNS(ned.svg.ns, "rect");
		this.eHeaderBack.setAttribute("width", "100%");
		this.eHeaderBack.setAttribute("height", headerSize);
		this.eHeader.appendChild(this.eHeaderBack);

		this.eHeaderText = document.createElementNS(ned.svg.ns, "text");
		this.eHeaderText.appendChild(document.createTextNode(sTitle));
		this.eHeaderText.setAttribute("x", "50%");
		this.eHeaderText.setAttribute("y", headerSize - 4); // padding to the bottom text
		this.eHeader.appendChild(this.eHeaderText);

		// ****************** inputs and outputs ******************
		this.eInputs = document.createElementNS(ned.svg.ns, "svg");
		this.eInputs.setAttribute("class", "Inputs");
		this.eInputs.setAttribute("overflow", "visible");
		this.eInputs.setAttribute("x", "0%");
		this.eInputs.setAttribute("y", headerSize);
		this.eRoot.appendChild(this.eInputs);

		this.eOutputs = document.createElementNS(ned.svg.ns, "svg");
		this.eOutputs.setAttribute("class", "Outputs");
		this.eOutputs.setAttribute("overflow", "visible");
		this.eOutputs.setAttribute("x", "100%");
		this.eOutputs.setAttribute("y", headerSize);
		this.eRoot.appendChild(this.eOutputs);

		// ****************** foreign objects ******************
		this.eForeign = document.createElementNS(ned.svg.ns, "foreignObject");
		this.eForeign.setAttribute("x", "16");
		this.eForeign.setAttribute("y", headerSize);
		this.eRoot.appendChild(this.eForeign);

		// ****************** foreign object test ******************
		this.eSelect = document.createElement("select");
		this.eForeign.appendChild(this.eSelect);

		for (let i = 0; i < 5; ++i)
		{
			var eOption = document.createElement("option");
			eOption.text = "test " + i;
			this.eSelect.appendChild(eOption);
		}
		this.eText = document.createElement("textarea");
		this.eForeign.appendChild(this.eText);
	},

	addInput(name) {
		return this.addConnection (name, true);
	},

	addOutput(name) {
		return this.addConnection (name, false);
	},

	addConnection (name, isInput) {
		var conn = Object.create (Ned.Connector);
		if (isInput) this.inputs.push(conn);
		else this.outputs.push(conn);

		conn.init(this, name, isInput);
		//TODO resize / position foreign object

		return conn;
	},

	updatePaths() {
		for (let c of this.inputs) c.updatePaths();
		for (let c of this.outputs) c.updatePaths();
	},

	get position() {
		return {
			x: this.eRoot.getAttribute("x"),
			y: this.eRoot.getAttribute("y")
		};
	},
	set position(pos) {
		if (this.editor.snapping) {
			var snp = this.editor.snapping;
			pos.x = Math.round(pos.x / snp) * snp;
			pos.y = Math.round(pos.y / snp) * snp;
		}

		this.eRoot.setAttribute("x", pos.x);
		this.eRoot.setAttribute("y", pos.y);
		this.updatePaths();
	},

	get width() {
		return this.eRoot.getAttribute("width");
	},
	set width(v) {
		this.eRoot.setAttribute("width", v);
		//TODO resize / position foreign object
	},

	get height() {
		return this.eRoot.getAttribute("height");
	},
	set height(v) {
		this.eRoot.setAttribute("height", v);
		//TODO resize / position foreign object
	},

	toTop() {
		this.editor.nodegroup.removeChild(this.eRoot);
		this.editor.nodegroup.appendChild(this.eRoot);
	},

	beginNodeDrag(e) {
		// we can only drag when the left mouse button is pressed
		if (e.button != 0) {
			return;
		}

		e.stopPropagation();

		this.toTop();
		var pos = this.position;
		var screenPos = this.editor.screenToWorld({ x: e.pageX, y: e.pageY });
		var offsetX = pos.x - screenPos.x;
		var offsetY = pos.y - screenPos.y;

		var onNodeDragMouseMove = (e) => {
			e.stopPropagation();
			e.preventDefault();
			var screenPos = this.editor.screenToWorld({ x: e.pageX, y: e.pageY });
			this.position = { x: screenPos.x + offsetX, y: screenPos.y + offsetY };
		};

		var onNodeDragMouseUp = (e) => {
			e.stopPropagation();
			e.preventDefault();

			window.removeEventListener("mousemove", onNodeDragMouseMove);
			window.removeEventListener("mouseup",  onNodeDragMouseUp);
		};

		window.addEventListener("mousemove", onNodeDragMouseMove);
		window.addEventListener("mouseup", onNodeDragMouseUp);
	}
};

/**************************
 * Node Connector Object
 **************************/
Ned.Connector = {
	init(node, name, isInput) {
		this.node = node;
		this.isInput = isInput;
		this.paths = [];

		var neRoot = isInput ? node.eInputs : node.eOutputs;

		// ****************** root ******************
		this.eRoot = document.createElementNS(this.editor.svg.ns, "svg");
		this.eRoot.setAttribute("class", isInput ? "Input" : "Output");
		this.eRoot.setAttribute("overflow", "visible");
		neRoot.appendChild(this.eRoot);

		// ****************** text ******************
		this.eText = document.createElementNS(this.editor.svg.ns, "text");
		this.eText.appendChild(document.createTextNode(name));
		this.eRoot.appendChild(this.eText);

		// ****************** dot ******************
		this.eDot = document.createElementNS(this.editor.svg.ns, "circle");
		this.eDot.ref = this; // we set the refference on the object we want to be able to connect to
		this.eDot.addEventListener("mousedown", (e) => { this.beginConnDrag(e); });
		this.eDot.addEventListener("mouseenter", (e) => { this.addHoverClass(); });
		this.eDot.addEventListener("mouseleave", (e) => { this.removeHoverClass(); });
		this.eRoot.appendChild(this.eDot);

		this.updatePosition();
	},

	addHoverClass() {
		var conn = this.editor.dragConn;
		if (!conn || (this.isInput === conn.isInput)) return;

		// we only add the class if we are dragging and the dragging connector isn't the same type of this connector
		this.eDot.setAttribute("class", "ConnHover");
	},
	removeHoverClass() {
		// on mouse leave we'll always remove the class
		this.eDot.removeAttribute("class");
	},

	updatePosition() {
		var rect = this.eRoot.getBoundingClientRect();

		var i = this.index;
		var y = (rect.height) + (rect.height * 1.5 * i)
		this.eRoot.setAttribute("y", y);
	},

	updatePaths() {
		for(let p of this.paths) p.update();
	},
	addPath(path) {
		if (this.hasSinglePath) {
			// destroy all current paths
			for(let p of this.paths) p.destroy();

			// clear the array
			this.paths = [];
		}

		this.paths.push(path);
	},
	removePath(path) {
		var index = this.paths.indexOf(path);
		if(index != -1) {
			this.paths.splice(index, 1);
		}
	},

	get editor() {
		return this.node.editor;
	},

	get index() {
		var list = (this.isInput) ? this.node.inputs : this.node.outputs;
		return list.indexOf(this);
	},

	get hasSinglePath() {
		return (this.isInput) ? this.editor.singleInputs : this.editor.singleOutputs;
	},
	get position() {
		var rect = this.eDot.getBoundingClientRect();
		// we need to keep the svg position in mind
		// and center it
		var screenPos = {
			x: rect.left + (rect.width / 2),
			y: rect.top + (rect.height / 2)
		};
		return this.editor.screenToWorld(screenPos);
	},

	// path parameter is optional and a path is created if none is supplied
	connectTo(conn, path) {
		if (this.isInput === conn.isInput) {
			//can't connect 2 inputs or 2 outputs
			if (path) path.destroy();
			return false;
		}

		if (!path) {
			var path = Object.create(Ned.Path);
			path.init(this);
		}
		path.setFinalConn(conn);

		// check if we don't already have a path like this
		if (this.paths.find((p) => path.equals(p))) {
			// this path already exists so delete it
			path.destroy();
			return false;
		}

		// remember the new path
		//add path to conn paths list to update it
		this.addPath(path);
		conn.addPath(path);
		return true;
	},

	beginConnDrag(e) {
		// we can only drag when the left mouse button is pressed
		if (e.button != 0) {
			return;
		}

		e.stopPropagation();
		e.preventDefault();

		this.editor.dragConn = this;
		var path = Object.create(Ned.Path);
		path.init(this);

		var onConnDragMouseMove = (e) => {
			e.stopPropagation();
			e.preventDefault();
			var mouseOffset = 3; // this offset is so the path isn't under the mouse when we release it. Else it isn't possible to get the underlying connector
			var screenPos = this.editor.screenToWorld({ x: e.pageX, y: e.pageY });
			var pos = {	x: screenPos.x, y: screenPos.y + mouseOffset };
			path.updateWithPos(pos);
		}

		var onConnDragMouseUp = (e) => {
			e.stopPropagation();
			e.preventDefault();

			this.editor.dragConn = null;
			var conn = e.target.ref; // get the saved refence to connect
			if (Ned.Connector.isPrototypeOf(conn)) {
				this.connectTo(conn, path);
				conn.removeHoverClass(); // we've stopped dragging so we'll make sure the hover class is removed
			} else {
				path.destroy();
			}

			window.removeEventListener("mousemove", onConnDragMouseMove);
			window.removeEventListener("mouseup", onConnDragMouseUp);
		};

		window.addEventListener("mousemove", onConnDragMouseMove);
		window.addEventListener("mouseup", onConnDragMouseUp);
	}
};

/**************************
 * Node path Object
 **************************/
Ned.Path = {
	init(conn) {
		this.editor = conn.editor;
		this.input = conn.isInput ? conn : null;
		this.output = conn.isInput ? null : conn;

		this.ePath = document.createElementNS(this.editor.svg.ns, "path");
		this.ePath.setAttribute("class", "Path");
		this.ePath.addEventListener("click", (e) => { this.onClicked(e); });

		this.editor.pathGroup.appendChild(this.ePath);

		var pos = conn.position;
		this.updateWithPos(pos);
	},

	destroy() {
		if (this.input) this.input.removePath(this);
		if (this.output) this.output.removePath(this);
		this.editor.pathGroup.removeChild(this.ePath);
		this.ePath = null;
	},

	setFinalConn(conn) {
		// if the input is set then set the output and vice versa
		if (this.input) this.output = conn;
		else this.input = conn;

		this.update();
	},

	equals(p) {
		return this.input === p.input && this.output === p.output;
	},

	onClicked(e) {
		// we can only drag when the left mouse button is pressed
		if (e.button != 0) {
			return;
		}

		this.destroy();
	},

	update() {
		this.updateWithPos ((this.input || this.output).position);
	},
	updateWithPos(pos) {
		var pos1 = this.output ? this.output.position : pos;
		var pos2 = this.input ? this.input.position : pos;
		this.setCurve (pos1.x, pos1.y, pos2.x, pos2.y);
	},

	setCurve(x1, y1, x2, y2) {
		var dif = Math.abs(x1 - x2) / 1.5,
			str = "M" + x1 + "," + y1 + " C" +	//MoveTo
				(x1 + dif) + "," + y1 + " " +	//First Control Point
				(x2 - dif) + "," + y2 + " " +	//Second Control Point
				(x2) + "," + y2;				//End Point

		this.ePath.setAttribute('d', str);
	}
}
<|endoftext|>


<commit_before>
var request = require('request');
const pad = require('pad');

const nhlStandingsURL = "https://statsapi.web.nhl.com/api/v1/standings?season=20172018";

function updateNHLStandings(completion) {
    request(nhlStandingsURL, function (error, response, body) {
        if (!error) {

            console.log("");

			const jsonData = JSON.parse(body);

			const standings = makeNHLStandings(jsonData.records);

            const scores = makeNHLScores(jsonData.records);

            console.log("");

			completion(standings, scores);
        } else {
            console.log("Error: " + error);
			completion("Det sket sig. :-( " + error);
        }
    });
}

function makeNHLStandings(records) {

    var output = "```\nNHL Standings\n\n";

	var conferences = {
		"Eastern": [],
		"Western": []
	};

	for (var r = 0; r < records.length; r++) {
		const record = records[r];
		const conferenceName = record.conference.name;
		for (var t = 0; t < record.teamRecords.length; t++) {
			const teamRecord = record.teamRecords[t];
			const teamName = teamRecord.team.name;
			const conferenceRank = teamRecord.conferenceRank;
			const points = teamRecord.points;
			conferences[conferenceName].push({
				name: teamName,
				rank: conferenceRank,
				points: points
			});
		}
	}

	conferences["Eastern"].sort(function(a, b) {
		return a.rank - b.rank;
	});
	conferences["Western"].sort(function(a, b) {
		return a.rank - b.rank;
	});

	output += pad("WESTERN CONFERENCE", 25) + "         EASTERN CONFERENCE\n\n";

	for (var i = 0; i < 16; i++) {
		const position = pad(2, String(i + 1));
		if (i < 15) {
			const westernTeam = conferences["Western"][i];
			output += pad(position + ". " + westernTeam.name, 25) + pad(" " + westernTeam.points + "p", 5);
		} else {
			output += pad(" ", 30);
		}
		const easternTeam = conferences["Eastern"][i];
		output += "    " + pad(position + ". " + easternTeam.name, 25) + pad(" " + easternTeam.points + "p", 5);

		output += "\n";
	}

    output += "```\n";

    return output;
}

var persons = [
    "dan", "elin", "eschmidt", "fredrik.bystam", "john", "jonasskold", "linda", "martin", "robertaxelsson", "schlyter"
]

var picks = {
    "dan": {
        eastern: ["nyr", "det", "njd", "tor", "mtl", "pit", "fla", "ott", "bos", "car", "wsh", "buf", "cbj", "nyi", "tbl", "phi"],
        western: ["col", "edm", "van", "ana", "sjs", "lak", "cgy", "chi", "ari", "wpg", "dal", "vgk", "nsh", "min", "stl"]
    },
    "elin": {
        eastern: ["det", "pit", "phi", "tbl", "wsh", "nyr", "nyi", "fla", "bos", "buf", "car", "cbj", "mtl", "njd", "ott","tor"],
        western: ["sjs", "chi", "dal", "ana", "stl", "min", "nsh", "ari", "lak", "col", "van", "vgk", "cgy", "wpg", "edm"]
    },
    "eschmidt": {
        eastern: ["pit", "bos", "nyr", "ott", "det", "wsh", "tbl", "cbj", "nyi", "tor", "mtl", "phi", "buf", "fla", "car", "njd"],
        western: ["nsh", "chi", "ana", "sjs", "lak", "vgk", "edm", "min", "cgy", "stl", "van", "wpg", "dal", "col", "ari"]
    },
    "fredrik.bystam": {
        eastern: ["mtl", "wsh", "cbj", "pit", "ott", "nyi", "bos", "tor", "nyr", "fla", "car", "tbl", "buf", "phi", "njd", "det"],
        western: ["edm", "ana", "stl", "chi", "min", "vgk", "nsh", "lak", "dal", "wpg", "sjs", "ari", "van", "cgy", "col"]
    },
    "john": {
        eastern: ["pit", "tbl", "wsh", "tor", "nyr", "ott", "mtl", "cbj", "bos", "nyi", "phi", "fla", "njd", "car", "buf", "det"],
        western: ["edm", "nsh", "ana", "chi", "min", "sjs", "stl", "wpg", "dal", "lak", "ari", "cgy", "vgk", "van", "col"]
    },
    "jonasskold": {
        eastern: ["wsh", "pit", "nyr", "bos", "mtl", "tbl", "nyi", "cbj", "det", "ott", "tor", "car", "njd", "fla", "phi", "buf"],
        western: ["stl", "chi", "ana", "nsh", "dal", "min", "edm", "lak", "sjs", "van", "vgk", "cgy", "wpg", "col", "ari"]
    },
    "linda": {
        eastern: ["mtl", "wsh", "pit", "ott", "cbj", "tbl", "nyr", "bos", "tor", "car", "det", "buf", "fla", "njd", "nyi", "phi"],
        western: ["chi", "ana", "edm", "sjs", "stl", "nsh", "col", "cgy", "min", "vgk", "ari", "dal", "lak", "van", "wpg"]
    },
    "martin": {
        eastern: ["wsh", "pit", "cbj", "mtl", "tor", "nyr", "tbl", "car", "nyi", "ott", "bos", "phi", "fla", "det", "njd", "buf"],
        western: ["edm", "ana", "sjs", "nsh", "min", "stl", "lak", "dal", "wpg", "chi", "ari", "cgy", "van", "vgk", "col"]
    },
    "robertaxelsson": {
        eastern: ["pit", "wsh", "bos", "nyr", "cbj", "mtl", "ott", "phi", "det", "tor", "nyi", "njd", "fla", "car", "tbl", "buf"],
        western: ["ana", "nsh", "chi", "min", "stl", "edm", "sjs", "cgy", "vgk", "lak", "ari", "col", "dal", "wpg", "van"]
    },
    "schlyter": {
        eastern: ["wsh", "nyr", "cbj", "pit", "bos", "mtl", "ott", "tor", "car", "det", "phi", "nyi", "tbl", "njd", "fla", "buf"],
        western: ["chi", "ana", "cgy", "min", "sjs", "stl", "edm", "nsh", "lak", "dal", "van", "vgk", "col", "wpg", "ari"]
    }
};

var nhlTeams = {
    "ana": "Anaheim Ducks",
    "ari": "Arizona Coyotes",
    "bos": "Boston Bruins",
    "buf": "Buffalo Sabres",
    "car": "Carolina Hurricanes",
    "cbj": "Columbus Blue Jackets",
    "cgy": "Calgary Flames",
    "chi": "Chicago Blackhawks",
    "col": "Colorado Avalanche",
    "dal": "Dallas Stars",
    "det": "Detroit Red Wings",
    "edm": "Edmonton Oilers",
    "fla": "Florida Panthers",
    "lak": "Los Angeles Kings",
    "min": "Minnesota Wild",
    "mtl": "Montreal Canadiens",
    "njd": "New Jersey Devils",
    "nsh": "Nashville Predators",
    "nyi": "New York Islanders",
    "nyr": "New York Rangers",
    "ott": "Ottawa Senators",
    "phi": "Philadelphia Flyers",
    "pit": "Pittsburgh Penguins",
    "sjs": "San Jose Sharks",
    "stl": "St Louis Blues",
    "tbl": "Tampa Bay Lightning",
    "tor": "Toronto Maple Leafs",
    "van": "Vancouver Canucks",
    "vgk": "Vegas Golden Knights",
    "wpg": "Winnipeg Jets",
    "wsh": "Washington Capitals"
};

function calculateScores(standings) {
    var scores = [];
    for (var index in persons) {
        const person = persons[index];
        const score = {
            person: person,
            points: calculateScore(person, standings)
        };
        scores.push(score)
    }
    return scores.sort(function (a, b) {return b.points - a.points});
}

function calculateScore(person, standings) {
    var score = 0;
    const picksForPerson = picks[person];
    score += calculateConferenceScore(picksForPerson.eastern, standings["Eastern"]);
    score += calculateConferenceScore(picksForPerson.western, standings["Western"]);
    return score;
}

function calculateConferenceScore(picksForPerson, standings) {
    var score = 0;

    for (var p = 0; p < picksForPerson.length; p++) {

        const currentPick = nhlTeams[picksForPerson[p]];
	    const picksBelowPick = picksForPerson.slice(p + 1).map(function (pick) { return nhlTeams[pick]; });

        const pickIndexInStandings = standings.findIndex(function (team) {
            return team.name == currentPick;
        });

        const teamsBelowPickInStandings = standings.slice(pickIndexInStandings + 1);

        const teamsInBoth = teamsBelowPickInStandings.filter(function (team) {
            return picksBelowPick.includes(team.name)
        });

        score += teamsInBoth.length;
    }

    return score;
}

function makeNHLScores(records) {

    var output = "```\nNHL Picks\n\n";

	var conferences = {
		"Eastern": [],
		"Western": []
	};

	for (var r = 0; r < records.length; r++) {
		const record = records[r];
		const conferenceName = record.conference.name;
		for (var t = 0; t < record.teamRecords.length; t++) {
			const teamRecord = record.teamRecords[t];
			const teamName = teamRecord.team.name;
			const conferenceRank = teamRecord.conferenceRank;
			const points = teamRecord.points;
			conferences[conferenceName].push({
				name: teamName,
				rank: conferenceRank,
				points: points
			});
		}
	}

	conferences["Eastern"].sort(function(a, b) {
		return a.rank - b.rank;
	});
	conferences["Western"].sort(function(a, b) {
		return a.rank - b.rank;
	});

    var scores = calculateScores(conferences);

    for (var i = 0; i < scores.length; i++) {
        const score = scores[i];
        output += ((i + 1) + ". " + score.person + " " + score.points + " p\n");
    }

    output += "```\n";

    return output;
}

exports.updateNHLStandings = updateNHLStandings;
<commit_msg>fixed formatting<commit_after>
var request = require('request');
const pad = require('pad');

const nhlStandingsURL = "https://statsapi.web.nhl.com/api/v1/standings?season=20172018";

function updateNHLStandings(completion) {
    request(nhlStandingsURL, function (error, response, body) {
        if (!error) {

            console.log("");

			const jsonData = JSON.parse(body);

			const standings = makeNHLStandings(jsonData.records);

            const scores = makeNHLScores(jsonData.records);

            console.log("");

			completion(standings, scores);
        } else {
            console.log("Error: " + error);
			completion("Det sket sig. :-( " + error);
        }
    });
}

function makeNHLStandings(records) {

    var output = "```\nNHL Standings\n\n";

	var conferences = {
		"Eastern": [],
		"Western": []
	};

	for (var r = 0; r < records.length; r++) {
		const record = records[r];
		const conferenceName = record.conference.name;
		for (var t = 0; t < record.teamRecords.length; t++) {
			const teamRecord = record.teamRecords[t];
			const teamName = teamRecord.team.name;
			const conferenceRank = teamRecord.conferenceRank;
			const points = teamRecord.points;
			conferences[conferenceName].push({
				name: teamName,
				rank: conferenceRank,
				points: points
			});
		}
	}

	conferences["Eastern"].sort(function(a, b) {
		return a.rank - b.rank;
	});
	conferences["Western"].sort(function(a, b) {
		return a.rank - b.rank;
	});

	output += pad("WESTERN CONFERENCE", 25) + "         EASTERN CONFERENCE\n\n";

	for (var i = 0; i < 16; i++) {
		const position = pad(2, String(i + 1));
		if (i < 15) {
			const westernTeam = conferences["Western"][i];
			output += pad(position + ". " + westernTeam.name, 25) + pad(" " + westernTeam.points + "p", 5);
		} else {
			output += pad(" ", 30);
		}
		const easternTeam = conferences["Eastern"][i];
		output += "    " + pad(position + ". " + easternTeam.name, 25) + pad(" " + easternTeam.points + "p", 5);

		output += "\n";
	}

    output += "```\n";

    return output;
}

var persons = [
    "dan", "elin", "eschmidt", "fredrik.bystam", "john", "jonasskold", "linda", "martin", "robertaxelsson", "schlyter"
]

var picks = {
    "dan": {
        eastern: ["nyr", "det", "njd", "tor", "mtl", "pit", "fla", "ott", "bos", "car", "wsh", "buf", "cbj", "nyi", "tbl", "phi"],
        western: ["col", "edm", "van", "ana", "sjs", "lak", "cgy", "chi", "ari", "wpg", "dal", "vgk", "nsh", "min", "stl"]
    },
    "elin": {
        eastern: ["det", "pit", "phi", "tbl", "wsh", "nyr", "nyi", "fla", "bos", "buf", "car", "cbj", "mtl", "njd", "ott","tor"],
        western: ["sjs", "chi", "dal", "ana", "stl", "min", "nsh", "ari", "lak", "col", "van", "vgk", "cgy", "wpg", "edm"]
    },
    "eschmidt": {
        eastern: ["pit", "bos", "nyr", "ott", "det", "wsh", "tbl", "cbj", "nyi", "tor", "mtl", "phi", "buf", "fla", "car", "njd"],
        western: ["nsh", "chi", "ana", "sjs", "lak", "vgk", "edm", "min", "cgy", "stl", "van", "wpg", "dal", "col", "ari"]
    },
    "fredrik.bystam": {
        eastern: ["mtl", "wsh", "cbj", "pit", "ott", "nyi", "bos", "tor", "nyr", "fla", "car", "tbl", "buf", "phi", "njd", "det"],
        western: ["edm", "ana", "stl", "chi", "min", "vgk", "nsh", "lak", "dal", "wpg", "sjs", "ari", "van", "cgy", "col"]
    },
    "john": {
        eastern: ["pit", "tbl", "wsh", "tor", "nyr", "ott", "mtl", "cbj", "bos", "nyi", "phi", "fla", "njd", "car", "buf", "det"],
        western: ["edm", "nsh", "ana", "chi", "min", "sjs", "stl", "wpg", "dal", "lak", "ari", "cgy", "vgk", "van", "col"]
    },
    "jonasskold": {
        eastern: ["wsh", "pit", "nyr", "bos", "mtl", "tbl", "nyi", "cbj", "det", "ott", "tor", "car", "njd", "fla", "phi", "buf"],
        western: ["stl", "chi", "ana", "nsh", "dal", "min", "edm", "lak", "sjs", "van", "vgk", "cgy", "wpg", "col", "ari"]
    },
    "linda": {
        eastern: ["mtl", "wsh", "pit", "ott", "cbj", "tbl", "nyr", "bos", "tor", "car", "det", "buf", "fla", "njd", "nyi", "phi"],
        western: ["chi", "ana", "edm", "sjs", "stl", "nsh", "col", "cgy", "min", "vgk", "ari", "dal", "lak", "van", "wpg"]
    },
    "martin": {
        eastern: ["wsh", "pit", "cbj", "mtl", "tor", "nyr", "tbl", "car", "nyi", "ott", "bos", "phi", "fla", "det", "njd", "buf"],
        western: ["edm", "ana", "sjs", "nsh", "min", "stl", "lak", "dal", "wpg", "chi", "ari", "cgy", "van", "vgk", "col"]
    },
    "robertaxelsson": {
        eastern: ["pit", "wsh", "bos", "nyr", "cbj", "mtl", "ott", "phi", "det", "tor", "nyi", "njd", "fla", "car", "tbl", "buf"],
        western: ["ana", "nsh", "chi", "min", "stl", "edm", "sjs", "cgy", "vgk", "lak", "ari", "col", "dal", "wpg", "van"]
    },
    "schlyter": {
        eastern: ["wsh", "nyr", "cbj", "pit", "bos", "mtl", "ott", "tor", "car", "det", "phi", "nyi", "tbl", "njd", "fla", "buf"],
        western: ["chi", "ana", "cgy", "min", "sjs", "stl", "edm", "nsh", "lak", "dal", "van", "vgk", "col", "wpg", "ari"]
    }
};

var nhlTeams = {
    "ana": "Anaheim Ducks",
    "ari": "Arizona Coyotes",
    "bos": "Boston Bruins",
    "buf": "Buffalo Sabres",
    "car": "Carolina Hurricanes",
    "cbj": "Columbus Blue Jackets",
    "cgy": "Calgary Flames",
    "chi": "Chicago Blackhawks",
    "col": "Colorado Avalanche",
    "dal": "Dallas Stars",
    "det": "Detroit Red Wings",
    "edm": "Edmonton Oilers",
    "fla": "Florida Panthers",
    "lak": "Los Angeles Kings",
    "min": "Minnesota Wild",
    "mtl": "Montreal Canadiens",
    "njd": "New Jersey Devils",
    "nsh": "Nashville Predators",
    "nyi": "New York Islanders",
    "nyr": "New York Rangers",
    "ott": "Ottawa Senators",
    "phi": "Philadelphia Flyers",
    "pit": "Pittsburgh Penguins",
    "sjs": "San Jose Sharks",
    "stl": "St Louis Blues",
    "tbl": "Tampa Bay Lightning",
    "tor": "Toronto Maple Leafs",
    "van": "Vancouver Canucks",
    "vgk": "Vegas Golden Knights",
    "wpg": "Winnipeg Jets",
    "wsh": "Washington Capitals"
};

function calculateScores(standings) {
    var scores = [];
    for (var index in persons) {
        const person = persons[index];
        const score = {
            person: person,
            points: calculateScore(person, standings)
        };
        scores.push(score)
    }
    return scores.sort(function (a, b) {return b.points - a.points});
}

function calculateScore(person, standings) {
    var score = 0;
    const picksForPerson = picks[person];
    score += calculateConferenceScore(picksForPerson.eastern, standings["Eastern"]);
    score += calculateConferenceScore(picksForPerson.western, standings["Western"]);
    return score;
}

function calculateConferenceScore(picksForPerson, standings) {
    var score = 0;

    for (var p = 0; p < picksForPerson.length; p++) {

        const currentPick = nhlTeams[picksForPerson[p]];
	    const picksBelowPick = picksForPerson.slice(p + 1).map(function (pick) { return nhlTeams[pick]; });

        const pickIndexInStandings = standings.findIndex(function (team) {
            return team.name == currentPick;
        });

        const teamsBelowPickInStandings = standings.slice(pickIndexInStandings + 1);

        const teamsInBoth = teamsBelowPickInStandings.filter(function (team) {
            return picksBelowPick.includes(team.name)
        });

        score += teamsInBoth.length;
    }

    return score;
}

function makeNHLScores(records) {

    var output = "```\nNHL Picks\n\n";

	var conferences = {
		"Eastern": [],
		"Western": []
	};

	for (var r = 0; r < records.length; r++) {
		const record = records[r];
		const conferenceName = record.conference.name;
		for (var t = 0; t < record.teamRecords.length; t++) {
			const teamRecord = record.teamRecords[t];
			const teamName = teamRecord.team.name;
			const conferenceRank = teamRecord.conferenceRank;
			const points = teamRecord.points;
			conferences[conferenceName].push({
				name: teamName,
				rank: conferenceRank,
				points: points
			});
		}
	}

	conferences["Eastern"].sort(function(a, b) {
		return a.rank - b.rank;
	});
	conferences["Western"].sort(function(a, b) {
		return a.rank - b.rank;
	});

    var scores = calculateScores(conferences);

    for (var i = 0; i < scores.length; i++) {
        const entry = scores[i];
        const position = pad(2, String(i + 1));
        const person = pad(entry.person, 20);
        const points = pad(2, String(entry.points));
        output += position + ". " + person + "   " + points + "p\n";
    }

    output += "```\n";

    return output;
}

exports.updateNHLStandings = updateNHLStandings;
<|endoftext|>


<commit_before>
/**
 * Module constants
 */

var ENTITIES_REGEX = /(^|\s|;|\.|,|:)(audio|sound|🔊|panorama|🌅|image|foto|picture|text|texto|📝|videosphere|video esfera|🎥|video|seconds|second|segundos|⏲|voiceover|voz en off|📢|chart|gráfico|📊|background|fondo|model|modelo)(\s|$|;|\.|,|:)/gi
var LOCATION_REGEX = /right|left|behind|front|atrás|frente|izquierda|derecha/i
var SIZE_REGEX = /tiny|small|large|huge|diminuto|pequeño|grande|enorme/i

module.exports = function (str) {
  return str
  .split('\n')
  .filter(function (p) { return /[0-9]+ (⏲|seconds?|segundos)/.test(p) })
  .map(getObjects)
}

function getObjects (p) {
  var entities = []
  var entity

  // Iterate over the paragraph and extract entities
  while ((entity = ENTITIES_REGEX.exec(p)) !== null) {
    entities.push({ type: entity[2].trim(), index: entity.index })
  }

  // Extract attributes for each entity
  return entities.map(function (entity, i) {
    // for backwards words I'm going back to the previous entity and add the length
    var sp = i !== 0
    ? p.substring(entities[i - 1].index + entities[i - 1].type.length, entities[i].index + entities[i].type.length + 1)
    : p.substring(0, entities[i].index + entities[i].type.length + 1)

    // special case for duration
    var match
    if (entity.type === 'seconds' || entity.type === 'second' || entity.type === '⏲' || entity.type === 'segundos') {
      match = sp.match(/[0-9]+ (⏲|seconds?|segundos)/)
      if (!match) return false

      return {
        type: 'duration',
        value: parseInt(match[0].replace(/(⏲|seconds?|segundos)/, ''), 10)
      }
    } else if (entity.type === 'background') {
      match = sp.match(/(#[a-fA-F0-9]{3,6}|\w+) background/i)
      return {
        type: 'background',
        color: match[0].replace(' background', '')
      }
    }

    // Get the portion of the text relative to this entity
    sp = i === entities.length - 1
    ? p.substring(entity.index)
    : p.substring(entity.index, entities[i + 1].index + 1)

    var str = sp
    switch (entity.type) {
      case 'audio':
      case 'sound':
      case '🔊':
        return {
          type: 'audio',
          src: getUrl(str),
          position: getPosition(str)
        }
      case 'voiceover':
      case 'voz en off':
      case '📢':
        return {
          type: 'voiceover',
          text: getQuote(str)
        }
      case 'chart':
      case 'gráfico':
      case '📊':
        return {
          type: 'chart',
          src: getUrl(str),
          position: getPosition(str, 10, 10),
          rotation: getRotation(str),
          scale: getSize(str)
        }
      case 'panorama':
      case '🌅':
        return {
          type: 'panorama',
          src: getUrl(str)
        }
      case 'video':
        return {
          type: 'video',
          src: getUrl(str),
          position: getPosition(str),
          scale: getSize(str),
          rotation: getRotation(str)
        }
      case 'videosphere':
      case 'video esfera':
      case '🎥':
        return {
          type: 'videosphere',
          src: getUrl(str)
        }
      case 'image':
      case 'picture':
      case 'foto':
        return {
          type: 'image',
          src: getUrl(str),
          position: getPosition(str),
          scale: getSize(str),
          rotation: getRotation(str)
        }
      case 'text':
      case 'texto':
      case '📝':
        var text = getQuote(str)
        return {
          type: 'text',
          text: text,
          position: getPosition(str, 14, text.length / 30),
          scale: getSize(str).map(function (el) { return el * 5 }),
          rotation: getRotation(str)
        }
      case 'model':
      case 'modelo':
        const url = getUrl(str, /\.obj$/)
        const ext = url.split('.')
        const mtl = getUrl(str, /\.mtl$/)
        return {
          type: 'model',
          src: url,
          mtl: mtl,
          extension: ext[ext.length - 1],
          position: convertModelPosition(getPosition(str)),
          scale: getSize(str),
          rotation: getRotation(str)
        }
      case 'fondo':
        match = sp.match(/fondo (#[a-fA-F0-9]{3,6})/i)
        return {
          type: 'background',
          color: match[1]
        }

    }

    return entity
  })
  .filter(function (entity) { return !!entity })
}

function convertModelPosition (pos) {
  return [pos[0], pos[1] - 3, pos[2]]
}

function getUrl (str, validate) {
  var match = str.match(/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g)
  if (!match.length) return
  if (validate) {
    match = match.filter(function (m) { return validate.test(m) })
  }
  if (!match.length) return

  return match[0].toLowerCase()
}

function getQuote (str) {
  var match = str.match(/".*"/)
  if (!(match && match.length)) return
  return match[0].replace(/"/g, '')
}

function getPosition (str, width, height) {
  width = width || 0
  height = height || 0
  var match = str.match(LOCATION_REGEX)
  return getAbsPos(match && match.length ? match[0] : 'front', width, height)
}

function getSize (str) {
  var match = str.match(SIZE_REGEX)
  return getAbsSize(match && match.length ? match[0] : 'normal')
}

function getAbsPos (str, width, height) {
  var xSize = -0.5 * width
  var ySize = -0.5 * height
  switch (str) {
    case 'left':
    case 'izquierda':
      return [-8, 1.6 + ySize, -xSize]
    case 'right':
    case 'derecha':
      return [8, 1.6 + ySize, -xSize]
    case 'behind':
    case 'atrás':
      return [-xSize, 1.6 + ySize, 8]
    case 'front':
    case 'frente':
    default:
      return [0 + xSize, 1.6 + ySize, -8]
  }
}

function getRotation (str) {
  var match = str.match(LOCATION_REGEX)
  var pos = match && match.length ? match[0] : 'front'
  switch (pos) {
    case 'left':
    case 'izquierda':
      return [0, 90, 0]
    case 'right':
    case 'derecha':
      return [0, 180, 0]
    case 'behind':
    case 'atrás':
      return [0, 180, 0]
    case 'front':
    case 'frente':
      return [0, 0, 0]
    default:
      return [0, 0, 0]
  }
}

function getAbsSize (str) {
  switch (str) {
    case 'tiny':
    case 'diminuto':
      return [1 / 3, 1 / 3, 1 / 3]
    case 'small':
    case 'pequeño':
      return [1 / 2, 1 / 2, 1 / 2]
    case 'large':
    case 'grande':
      return [2, 2, 2]
    case 'huge':
    case 'enorme':
      return [3, 3, 3]
    default:
      return [1, 1, 1]
  }
}
<commit_msg>Removed toloewercase in urls<commit_after>
/**
 * Module constants
 */

var ENTITIES_REGEX = /(^|\s|;|\.|,|:)(audio|sound|🔊|panorama|🌅|image|foto|picture|text|texto|📝|videosphere|video esfera|🎥|video|seconds|second|segundos|⏲|voiceover|voz en off|📢|chart|gráfico|📊|background|fondo|model|modelo)(\s|$|;|\.|,|:)/gi
var LOCATION_REGEX = /right|left|behind|front|atrás|frente|izquierda|derecha/i
var SIZE_REGEX = /tiny|small|large|huge|diminuto|pequeño|grande|enorme/i

module.exports = function (str) {
  return str
  .split('\n')
  .filter(function (p) { return /[0-9]+ (⏲|seconds?|segundos)/.test(p) })
  .map(getObjects)
}

function getObjects (p) {
  var entities = []
  var entity

  // Iterate over the paragraph and extract entities
  while ((entity = ENTITIES_REGEX.exec(p)) !== null) {
    entities.push({ type: entity[2].trim(), index: entity.index })
  }

  // Extract attributes for each entity
  return entities.map(function (entity, i) {
    // for backwards words I'm going back to the previous entity and add the length
    var sp = i !== 0
    ? p.substring(entities[i - 1].index + entities[i - 1].type.length, entities[i].index + entities[i].type.length + 1)
    : p.substring(0, entities[i].index + entities[i].type.length + 1)

    // special case for duration
    var match
    if (entity.type === 'seconds' || entity.type === 'second' || entity.type === '⏲' || entity.type === 'segundos') {
      match = sp.match(/[0-9]+ (⏲|seconds?|segundos)/)
      if (!match) return false

      return {
        type: 'duration',
        value: parseInt(match[0].replace(/(⏲|seconds?|segundos)/, ''), 10)
      }
    } else if (entity.type === 'background') {
      match = sp.match(/(#[a-fA-F0-9]{3,6}|\w+) background/i)
      return {
        type: 'background',
        color: match[0].replace(' background', '')
      }
    }

    // Get the portion of the text relative to this entity
    sp = i === entities.length - 1
    ? p.substring(entity.index)
    : p.substring(entity.index, entities[i + 1].index + 1)

    var str = sp
    switch (entity.type) {
      case 'audio':
      case 'sound':
      case '🔊':
        return {
          type: 'audio',
          src: getUrl(str),
          position: getPosition(str)
        }
      case 'voiceover':
      case 'voz en off':
      case '📢':
        return {
          type: 'voiceover',
          text: getQuote(str)
        }
      case 'chart':
      case 'gráfico':
      case '📊':
        return {
          type: 'chart',
          src: getUrl(str),
          position: getPosition(str, 10, 10),
          rotation: getRotation(str),
          scale: getSize(str)
        }
      case 'panorama':
      case '🌅':
        return {
          type: 'panorama',
          src: getUrl(str)
        }
      case 'video':
        return {
          type: 'video',
          src: getUrl(str),
          position: getPosition(str),
          scale: getSize(str),
          rotation: getRotation(str)
        }
      case 'videosphere':
      case 'video esfera':
      case '🎥':
        return {
          type: 'videosphere',
          src: getUrl(str)
        }
      case 'image':
      case 'picture':
      case 'foto':
        return {
          type: 'image',
          src: getUrl(str),
          position: getPosition(str),
          scale: getSize(str),
          rotation: getRotation(str)
        }
      case 'text':
      case 'texto':
      case '📝':
        var text = getQuote(str)
        return {
          type: 'text',
          text: text,
          position: getPosition(str, 14, text.length / 30),
          scale: getSize(str).map(function (el) { return el * 5 }),
          rotation: getRotation(str)
        }
      case 'model':
      case 'modelo':
        const url = getUrl(str, /\.obj$/)
        const ext = url.split('.')
        const mtl = getUrl(str, /\.mtl$/)
        return {
          type: 'model',
          src: url,
          mtl: mtl,
          extension: ext[ext.length - 1],
          position: convertModelPosition(getPosition(str)),
          scale: getSize(str),
          rotation: getRotation(str)
        }
      case 'fondo':
        match = sp.match(/fondo (#[a-fA-F0-9]{3,6})/i)
        return {
          type: 'background',
          color: match[1]
        }

    }

    return entity
  })
  .filter(function (entity) { return !!entity })
}

function convertModelPosition (pos) {
  return [pos[0], pos[1] - 3, pos[2]]
}

function getUrl (str, validate) {
  var match = str.match(/https?:\/\/[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g)
  if (!match.length) return
  if (validate) {
    match = match.filter(function (m) { return validate.test(m) })
  }
  if (!match.length) return

  return match[0]
}

function getQuote (str) {
  var match = str.match(/".*"/)
  if (!(match && match.length)) return
  return match[0].replace(/"/g, '')
}

function getPosition (str, width, height) {
  width = width || 0
  height = height || 0
  var match = str.match(LOCATION_REGEX)
  return getAbsPos(match && match.length ? match[0] : 'front', width, height)
}

function getSize (str) {
  var match = str.match(SIZE_REGEX)
  return getAbsSize(match && match.length ? match[0] : 'normal')
}

function getAbsPos (str, width, height) {
  var xSize = -0.5 * width
  var ySize = -0.5 * height
  switch (str) {
    case 'left':
    case 'izquierda':
      return [-8, 1.6 + ySize, -xSize]
    case 'right':
    case 'derecha':
      return [8, 1.6 + ySize, -xSize]
    case 'behind':
    case 'atrás':
      return [-xSize, 1.6 + ySize, 8]
    case 'front':
    case 'frente':
    default:
      return [0 + xSize, 1.6 + ySize, -8]
  }
}

function getRotation (str) {
  var match = str.match(LOCATION_REGEX)
  var pos = match && match.length ? match[0] : 'front'
  switch (pos) {
    case 'left':
    case 'izquierda':
      return [0, 90, 0]
    case 'right':
    case 'derecha':
      return [0, 180, 0]
    case 'behind':
    case 'atrás':
      return [0, 180, 0]
    case 'front':
    case 'frente':
      return [0, 0, 0]
    default:
      return [0, 0, 0]
  }
}

function getAbsSize (str) {
  switch (str) {
    case 'tiny':
    case 'diminuto':
      return [1 / 3, 1 / 3, 1 / 3]
    case 'small':
    case 'pequeño':
      return [1 / 2, 1 / 2, 1 / 2]
    case 'large':
    case 'grande':
      return [2, 2, 2]
    case 'huge':
    case 'enorme':
      return [3, 3, 3]
    default:
      return [1, 1, 1]
  }
}
<|endoftext|>


<commit_before>
process.title = "npm"

if (require.main === module) {
  console.error(["It looks like you're doing 'node npm.js'."
                ,"Don't do that.  Instead, run 'make install'"
                ,"and then use the 'npm' command line utility."
                ].join("\n"))
  process.exit(1)
}

var npm = exports
  , config = require("./lib/config")
  , set = require("./lib/utils/set")
  , get = require("./lib/utils/get")
  , ini = require("./lib/utils/ini")
  , log = require("./lib/utils/log")
  , fs = require("./lib/utils/graceful-fs")
  , path = require("path")
  , mkdir = require("./lib/utils/mkdir-p")
  , abbrev = require("./lib/utils/abbrev")

npm.commands = {}
npm.ELIFECYCLE = {}
npm.E404 = {}

try {
  // startup, ok to do this synchronously
  var j = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"))+"")
  npm.version = j.version
  npm.nodeVersionRequired = j.engines.node
} catch (ex) {
  log(ex, "error reading version")
  npm.version = ex
}

var commandCache = {}
  // short names for common things
  , aliases = { "rm" : "uninstall"
              , "r" : "uninstall"
              , "rb" : "rebuild"
              , "bn" : "bundle"
              , "list" : "ls"
              , "ln" : "link"
              , "i" : "install"
              , "u" : "update"
              , "up" : "update"
              , "c" : "config"
              }
  , aliasNames = Object.keys(aliases)
  // these are filenames in ./lib
  , cmdList = [ "install"
              , "activate"
              , "deactivate"
              , "uninstall"
              , "build"
              , "link"
              , "publish"
              , "tag"
              , "adduser"
              , "config"
              , "help"
              , "cache"
              , "test"
              , "stop"
              , "start"
              , "restart"
              , "unpublish"
              , "ls"
              , "owner"
              , "update"
              , "update-dependents"
              , "view"
              , "repl"
              , "rebuild"
              , "bundle"
              , "outdated"
              , "init"
              ]
  , fullList = cmdList.concat(aliasNames)
  , abbrevs = abbrev(fullList)

Object.keys(abbrevs).forEach(function (c) {
  Object.defineProperty(npm.commands, c, { get : function () {
    var a = abbrevs[c]
    if (aliases[a]) a = aliases[a]
    if (commandCache[a]) return commandCache[a]
    return commandCache[a] = require(__dirname+"/lib/"+a)
  }, enumerable: fullList.indexOf(c) !== -1 })
})

var loaded = false
npm.load = function (conf, cb) {
  if (!cb && typeof conf === "function") cb = conf , conf = {}
  if (loaded) return cb()
  loaded = true
  // don't assume that npm is installed in any particular spot, since this
  // might conceivably be a bootstrap attempt.
  log.waitForConfig()
  ini.resolveConfigs(conf, function (er) {
    if (er) return cb(er)
    mkdir(npm.tmp, cb)
  })
}

// Local store for package data, so it won't have to be fetched/read more than
// once in a single pass.  TODO: cache this to disk somewhere when we're using
// the registry, to cut down on HTTP calls.
var registry = {}
npm.set = function (key, val) {
  if (typeof key === "object" && !val && key._id) {
    val = key
    key = key._id
  }
  return set(registry, key, val)
}
npm.get = function (key) { return get(registry, key) }

var path = require("path")
npm.config =
  { get : function (key) { return ini.get(key) }
  , set : function (key, val) { return ini.set(key, val, "cli") }
  , del : function (key, val) { return ini.del(key, val, "cli") }
  }

Object.defineProperty(npm, "root",
  { get : function () { return npm.config.get("root") }
  , set : function (r) {
      r = r.charAt(0) === "/" ? r
        : path.join(process.execPath, "..", "..", r)
      return npm.config.set("root", r)
    }
  , enumerable : true
  })
Object.defineProperty(npm, "dir",
  { get : function () { return path.join(npm.root, ".npm") }
  , enumerable : true
  })
Object.defineProperty(npm, "cache",
  { get : function () { return path.join(npm.root, ".npm", ".cache") }
  , enumerable : true
  })
var tmpFolder
Object.defineProperty(npm, "tmp",
  { get : function () {
      if (!tmpFolder) tmpFolder = "npm-"+Date.now()
      return path.join(npm.config.get("tmproot"), tmpFolder)
    }
  , enumerable : true
  })

if (process.getuid() === 0) {
  log.error( "\nRunning npm as root is not recommended!\n"
           + "Seriously, don't do this!\n", "sudon't!")
}
<commit_msg>make the npm object an EventEmitter<commit_after>
process.title = "npm"

if (require.main === module) {
  console.error(["It looks like you're doing 'node npm.js'."
                ,"Don't do that.  Instead, run 'make install'"
                ,"and then use the 'npm' command line utility."
                ].join("\n"))
  process.exit(1)
}

var EventEmitter = require("events").EventEmitter
  , npm = module.exports = new EventEmitter
  , config = require("./lib/config")
  , set = require("./lib/utils/set")
  , get = require("./lib/utils/get")
  , ini = require("./lib/utils/ini")
  , log = require("./lib/utils/log")
  , fs = require("./lib/utils/graceful-fs")
  , path = require("path")
  , mkdir = require("./lib/utils/mkdir-p")
  , abbrev = require("./lib/utils/abbrev")

npm.commands = {}
npm.ELIFECYCLE = {}
npm.E404 = {}

try {
  // startup, ok to do this synchronously
  var j = JSON.parse(fs.readFileSync(path.join(__dirname, "package.json"))+"")
  npm.version = j.version
  npm.nodeVersionRequired = j.engines.node
} catch (ex) {
  log(ex, "error reading version")
  npm.version = ex
}

var commandCache = {}
  // short names for common things
  , aliases = { "rm" : "uninstall"
              , "r" : "uninstall"
              , "rb" : "rebuild"
              , "bn" : "bundle"
              , "list" : "ls"
              , "ln" : "link"
              , "i" : "install"
              , "u" : "update"
              , "up" : "update"
              , "c" : "config"
              }
  , aliasNames = Object.keys(aliases)
  // these are filenames in ./lib
  , cmdList = [ "install"
              , "activate"
              , "deactivate"
              , "uninstall"
              , "build"
              , "link"
              , "publish"
              , "tag"
              , "adduser"
              , "config"
              , "help"
              , "cache"
              , "test"
              , "stop"
              , "start"
              , "restart"
              , "unpublish"
              , "ls"
              , "owner"
              , "update"
              , "update-dependents"
              , "view"
              , "repl"
              , "rebuild"
              , "bundle"
              , "outdated"
              , "init"
              ]
  , fullList = cmdList.concat(aliasNames)
  , abbrevs = abbrev(fullList)

Object.keys(abbrevs).forEach(function (c) {
  Object.defineProperty(npm.commands, c, { get : function () {
    var a = abbrevs[c]
    if (aliases[a]) a = aliases[a]
    if (commandCache[a]) return commandCache[a]
    return commandCache[a] = require(__dirname+"/lib/"+a)
  }, enumerable: fullList.indexOf(c) !== -1 })
})

var loaded = false
npm.load = function (conf, cb_) {
  if (!cb_ && typeof conf === "function") cb_ = conf , conf = {}
  function cb (er) { return cb_(er, npm) }
  if (loaded) return cb()
  loaded = true
  log.waitForConfig()
  ini.resolveConfigs(conf, function (er) {
    if (er) return cb(er)
    mkdir(npm.tmp, cb)
  })
}

// Local store for package data, so it won't have to be fetched/read more than
// once in a single pass.  TODO: cache this to disk somewhere when we're using
// the registry, to cut down on HTTP calls.
var registry = {}
npm.set = function (key, val) {
  if (typeof key === "object" && !val && key._id) {
    val = key
    key = key._id
  }
  return set(registry, key, val)
}
npm.get = function (key) { return get(registry, key) }

var path = require("path")
npm.config =
  { get : function (key) { return ini.get(key) }
  , set : function (key, val) { return ini.set(key, val, "cli") }
  , del : function (key, val) { return ini.del(key, val, "cli") }
  }

Object.defineProperty(npm, "root",
  { get : function () { return npm.config.get("root") }
  , set : function (r) {
      r = r.charAt(0) === "/" ? r
        : path.join(process.execPath, "..", "..", r)
      return npm.config.set("root", r)
    }
  , enumerable : true
  })
Object.defineProperty(npm, "dir",
  { get : function () { return path.join(npm.root, ".npm") }
  , enumerable : true
  })
Object.defineProperty(npm, "cache",
  { get : function () { return path.join(npm.root, ".npm", ".cache") }
  , enumerable : true
  })
var tmpFolder
Object.defineProperty(npm, "tmp",
  { get : function () {
      if (!tmpFolder) tmpFolder = "npm-"+Date.now()
      return path.join(npm.config.get("tmproot"), tmpFolder)
    }
  , enumerable : true
  })

if (process.getuid() === 0) {
  log.error( "\nRunning npm as root is not recommended!\n"
           + "Seriously, don't do this!\n", "sudon't!")
}
<|endoftext|>


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

    // Extract an image from the ora into an Image object
    function extractImage(path, zipfs, ondone, onerror) {
        var imgEntry = zipfs.find(path);
        if (imgEntry) {
            imgEntry.getData64URI('image/png', function (uri) {
                var imageObj = new Image();
                imageObj.onload = ondone;
                imageObj.src = uri;
            });
        } else if (onerror) {
            onerror();
        }
    }

    // Resize a canvas to the given size
    function resize(canvas, width, height, callback) {
        if(canvas.width <= width && canvas.height <= height) {
            callback();
        }

        var ctx = canvas.getContext('2d'),
            oldCanvas = canvas.toDataURL("image/png"),
            img = new Image(),
            aspect = canvas.width / canvas.height;

        if(aspect >= 1) {
            height = width / aspect;
        }
        else {
            width = height * aspect;
        }

        img.src = oldCanvas;
        img.onload = function () {
            canvas.width = width;
            canvas.height = height;
            ctx.clearRect(0, 0, width, height);
            ctx.drawImage(img, 0, 0, width, height);
            callback();
        };
    }

    // Layer object constructor.
    function Layer(width, height, name) {
        this.name = name;
        this.width = width || 0;
        this.height = height || 0;
        this.x = 0;
        this.y = 0;
        this.composite = 'svg:src-over';
        this.opacity = 1;
        this.visibility = 'visible';
    }

    // Draw layer onto a new canvas element
    Layer.prototype.toCanvas = function (canvas, width, height) {
        var tmpCanvas = canvas || document.createElement('canvas');
        tmpCanvas.width = width || this.width;
        tmpCanvas.height = height || this.height;

        var tmpCtx = tmpCanvas.getContext('2d');
        tmpCtx.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);
        tmpCtx.drawImage(this.image, this.x, this.y);
        return tmpCanvas;
    };

    // OraFile constructor
    function OraFile(width, height) {
        this.width = width || 0;
        this.height = height || 0;
        this.layers = [];
    }

    // Load the file contents from a blob
    // Based on the draft specification from May 2013
    // http://www.freedesktop.org/wiki/Specifications/OpenRaster/Draft/
    OraFile.prototype.load = function (blob, onload) {
        zip.workerScriptsPath = obj.ora.scriptsPath;
        zip.useWebWorkers = obj.ora.enableWorkers;

        var fs = new zip.fs.FS(),
            that = this;

        function loadLayers(image, ondone) {
            var layersLoaded = 0,
               layerElems = image.getElementsByTagName('stack')[0].getElementsByTagName('layer');

            var layerCount = layerElems.length;
            that.layers = [];

            var onExtract = function(layer) {
                return function() {
                    layer.image = this;
                    layer.width = this.width;
                    layer.height = this.height;

                    layersLoaded++;
                    if (layersLoaded === layerCount) {
                        ondone();
                    }
                };
            };

            for (var i = layerCount - 1; i >= 0; i--) {
                var layer = new Layer();
                var layerElement = layerElems[i];

                layer.name = layerElement.getAttribute('name');
                layer.x = layerElement.getAttribute('x') || 0;
                layer.y = layerElement.getAttribute('y') || 0;
                layer.composite = layerElement.getAttribute('composite-op') || 'svg:src-over';
                layer.opacity = layerElement.getAttribute('opacity') || 1;
                layer.visibility = layerElement.getAttribute('visibility') || 'visible';

                extractImage(layerElement.getAttribute('src'), fs, onExtract(layer));

                that.layers.push(layer);
            }
        }

        function loadStack(ondone) {
            var stackFile = fs.find('stack.xml');
            stackFile.getText(function (text) {
                var xml;
                // http://stackoverflow.com/questions/649614/xml-parsing-of-a-variable-string-in-javascript
                var parseXml;

                if (window.DOMParser) {
                    xml = ( new window.DOMParser() ).parseFromString(text, "text/xml");
                } else {
                    xml = new window.ActiveXObject("Microsoft.XMLDOM");
                    xml.async = false;
                    xml.loadXML(text);
                }

                var img = xml.getElementsByTagName('image')[0];
                that.width = img.getAttribute('w');
                that.height = img.getAttribute('h');

                loadLayers(img, ondone);
            });
        }

        function loadOra() {
            // keeping track of finished loading tasks
            var stepsDone = 0, steps = 3;
            var onDone = function () {
                stepsDone++;
                if (stepsDone === steps) {
                    onload();
                }
            };

            extractImage('Thumbnails/thumbnail.png', fs, function() {
                that.thumbnail = this;
                onDone();
            }, onDone);

            extractImage('mergedimage.png', fs, function() {
                that.prerendered = this;
                onDone();
            }, onDone);

            loadStack(onDone);
        }

        fs.importBlob(blob, loadOra);
    };

    OraFile.prototype.save = function(ondone) {
        zip.workerScriptsPath = obj.ora.scriptsPath;
        zip.useWebWorkers = obj.ora.enableWorkers;

        var fs = new zip.fs.FS(),
            thumbs = fs.root.addDirectory('Thumbnails'),
            data = fs.root.addDirectory('data'),
            tmpCanvas = document.createElement('canvas'),
            xmlDoc = document.implementation.createDocument(null, null, null),
            serializer = new XMLSerializer(),
            i = this.layers.length,
            layer, url, name, xelem, celem;

        fs.root.addText("mimetype", "image/openraster");

        xelem = xmlDoc.createElement('image');
        xelem.setAttribute('w', this.width);
        xelem.setAttribute('h', this.height);
        xmlDoc.appendChild(xelem);

        celem = xmlDoc.createElement('stack');
        xelem.appendChild(celem);
        xelem = celem;

        while(i) {
            layer = this.layers[i - 1];

            celem = xmlDoc.createElement('layer');
            celem.setAttribute('name', layer.name || ('layer' + i));
            celem.setAttribute('x', layer.x);
            celem.setAttribute('y', layer.y);
            celem.setAttribute('composite-op', layer.composite);
            celem.setAttribute('opacity', layer.opacity);
            celem.setAttribute('visibility', layer.visibility);

            url = layer.toCanvas().toDataURL('image/png');
            name = 'layer' + i + '.png';
            celem.setAttribute('src', 'data/' + name);
            data.addData64URI(name, url);
            xelem.appendChild(celem);
            i--;
        }

        fs.root.addText('stack.xml', serializer.serializeToString(xmlDoc));

        this.drawComposite(tmpCanvas);
        url = tmpCanvas.toDataURL('image/png');
        fs.root.addData64URI('mergedimage.png', url);

        resize(tmpCanvas, 256, 256, function() {
            url = tmpCanvas.toDataURL('image/png');
            thumbs.addData64URI('thumbnail.png', url);

            fs.exportBlob(ondone, 'image/openraster');
        });
    };

    // Draw the thumbnail into a canvas element
    OraFile.prototype.drawThumbnail = function (canvas) {
        if (this.thumbnail) {
            canvas.width = this.thumbnail.width;
            canvas.height = this.thumbnail.height;
            var context = canvas.getContext('2d');
            context.clearRect(0, 0, canvas.width, canvas.height);
            context.drawImage(this.thumbnail, 0, 0);
        }
    };

    // Draw the full size composite image from the layer data.
    // Uses the prerendered image if present and enabled
    OraFile.prototype.drawComposite = function (canvas, ondone) {
        canvas.width = this.width;
        canvas.height = this.height;

        if(!this.layers) {
            if(ondone) {
                ondone();
            }

            return;
        }

        var context = canvas.getContext('2d');

        context.clearRect(0, 0, canvas.width, canvas.height);

        if(obj.ora.enablePrerendered && this.prerendered) {
            context.drawImage(this.prerendered, 0, 0);
            return;
        }

        if (!obj.blending) {
            composeNoBlend(this, context, ondone);
            return;
        }

        if(!obj.ora.enableWorkers || !window.Worker) {
            compose(this, context, ondone);
            return;
        }

        composeWorkers(this, context, ondone);
    };

    function composeWorkers (oraFile, context, ondone) {
        var layerCache = [],
            startLayer = -1,
            worker, i, tmpCanvas;

        for (i = 0; i < oraFile.layers.length; i++) {
            if(oraFile.layers[i].visibility != 'hidden') {
                if(startLayer < 0) {
                    startLayer = i;
                }

                layerCache[i] = {
                    opacity: oraFile.layers[i].opacity,
                    composite: oraFile.layers[i].composite,
                    data: oraFile.layers[i]
                        .toCanvas(tmpCanvas, oraFile.width, oraFile.height)
                        .getContext('2d')
                };
            }
        }

        if(startLayer < 0) {
            if(ondone) {
                ondone();
            }
            return;
        }

        function onTaskDone(e) {
            if(e.data.result) {
                // paint result
                context.putImageData(e.data.result, 0, 0);
                if(ondone) {
                    ondone();
                }

                return;
            }

            var nextLayer = e.data.layer + 1;

            while(nextLayer < oraFile.layers.length && !layerCache[nextLayer]) {
                nextLayer++;
            }

            if(nextLayer >= oraFile.layers.length) {
                worker.postMessage({ done : true });
                return;
            }

            var nextBatch = {
                layer: nextLayer,
                src : layerCache[nextLayer].data.getImageData(0, 0, oraFile.width, oraFile.height),
                opacity: layerCache[nextLayer].opacity,
                filter: layerCache[nextLayer].composite
            };

            worker.postMessage(nextBatch);
        }

        worker = new Worker(obj.ora.scriptsPath + 'blender.js');
        worker.onmessage = onTaskDone;

        var initData = {
            layer: startLayer,
            src: layerCache[startLayer].data.getImageData(0, 0, oraFile.width, oraFile.height),
            opacity: layerCache[startLayer].opacity,
            filter: layerCache[startLayer].composite,
            dst: context.getImageData(0, 0, oraFile.width, oraFile.height)
        };

        worker.postMessage(initData);
    }

    function compose (oraFile, context, ondone) {
        var imgData = context.getImageData(0, 0, oraFile.width, oraFile.height),
            layerCount = oraFile.layers.length,
            layerIdx = 0,
            layer, tmpCanvas;

        while (layerCount > layerIdx) {
            layer = oraFile.layers[layerIdx];

            if (layer && layer.image && (layer.visibility === 'visible' || layer.visibility === undefined)) {
                var filter = obj.blending[layer.composite] || obj.blending.normal;
                var srcCanvas = layer.toCanvas(tmpCanvas, oraFile.width, oraFile.height);
                var src = srcCanvas.getContext('2d').getImageData(0, 0, srcCanvas.width, srcCanvas.height).data;
                obj.blending.blend(src, imgData.data, layer.opacity, filter);
            }

            layerIdx++;
        }

        context.putImageData(imgData, 0, 0);

        if(ondone) {
            ondone();
        }
    }

    function composeNoBlend(oraFile, context, ondone) {
        var layerCount = oraFile.layers.length,
            layerIdx = 0,
            layer;

        while (layerCount > layerIdx) {
            layer = oraFile.layers[layerIdx];
            if (layer && layer.image && (layer.visibility === 'visible' || layer.visibility === undefined)) {
                if (layer.opacity === undefined) {
                    context.globalAlpha = 1;
                } else {
                    context.globalAlpha = layer.opacity;
                }

                context.drawImage(layer.image, layer.x, layer.y);
            }

            layerIdx++;
        }

        if(ondone) {
            ondone();
        }
    }

    // Add a new layer to the image
    // index can optionally specify the position for the new layer
    OraFile.prototype.addLayer = function (name, index) {
        var layer = new Layer(this.width, this.height, name);
        if(index !== undefined && index < this.layers.length && index >= 0) {
            this.layers.splice(index, 0, layer);
        } else {
            this.layers.push(layer);
        }
        return layer;
    };

    // Create and populate an OraFile object from a blob
    // onload - callback with the loaded object as parameter
    function loadFile(blob, onload) {
        var oraFile = new OraFile();
        oraFile.load(blob, function() {
            onload(oraFile);
        });
    }

    obj.ora = {
        Ora : OraFile,
        OraLayer : Layer,
        load: loadFile,

        // enable use of prerendered image instead of layers (if present)
        enablePrerendered : true,
        enableWorkers : true,
        scriptsPath : ''
    };
})(this);<commit_msg>Fixed saving issues and worker leak<commit_after>(function (obj) {
    'use strict';

    // Extract an image from the ora into an Image object
    function extractImage(path, zipfs, ondone, onerror) {
        var imgEntry = zipfs.find(path);
        if (imgEntry) {
            imgEntry.getData64URI('image/png', function (uri) {
                var imageObj = new Image();
                imageObj.onload = ondone;
                imageObj.src = uri;
            });
        } else if (onerror) {
            onerror();
        }
    }

    // Resize a canvas to the given size
    function resize(canvas, width, height, callback) {
        if(canvas.width <= width && canvas.height <= height) {
            callback();
        }

        var ctx = canvas.getContext('2d'),
            oldCanvas = canvas.toDataURL("image/png"),
            img = new Image(),
            aspect = canvas.width / canvas.height;

        if(aspect >= 1) {
            height = width / aspect;
        }
        else {
            width = height * aspect;
        }

        img.src = oldCanvas;
        img.onload = function () {
            canvas.width = width;
            canvas.height = height;
            ctx.clearRect(0, 0, width, height);
            ctx.drawImage(img, 0, 0, width, height);
            callback();
        };
    }

    // Layer object constructor.
    function Layer(width, height, name) {
        this.name = name;
        this.width = width || 0;
        this.height = height || 0;
        this.x = 0;
        this.y = 0;
        this.composite = 'svg:src-over';
        this.opacity = 1;
        this.visibility = 'visible';
    }

    // Draw layer onto a new canvas element
    Layer.prototype.toCanvas = function (canvas, width, height, noOffset) {
        var tmpCanvas = canvas || document.createElement('canvas');
        tmpCanvas.width = width || this.width;
        tmpCanvas.height = height || this.height;

        var tmpCtx = tmpCanvas.getContext('2d');
        tmpCtx.clearRect(0, 0, tmpCanvas.width, tmpCanvas.height);
        if(noOffset) {
            tmpCtx.drawImage(this.image, 0, 0);
        } else {
            tmpCtx.drawImage(this.image, this.x, this.y);
        }
        return tmpCanvas;
    };

    // OraFile constructor
    function OraFile(width, height) {
        this.width = width || 0;
        this.height = height || 0;
        this.layers = [];
    }

    // Load the file contents from a blob
    // Based on the draft specification from May 2013
    // http://www.freedesktop.org/wiki/Specifications/OpenRaster/Draft/
    OraFile.prototype.load = function (blob, onload) {
        zip.workerScriptsPath = obj.ora.scriptsPath;
        zip.useWebWorkers = obj.ora.enableWorkers;

        var fs = new zip.fs.FS(),
            that = this;

        function loadLayers(image, ondone) {
            var layersLoaded = 0,
               layerElems = image.getElementsByTagName('stack')[0].getElementsByTagName('layer');

            var layerCount = layerElems.length;
            that.layers = [];

            var onExtract = function(layer) {
                return function() {
                    layer.image = this;
                    layer.width = this.width;
                    layer.height = this.height;

                    layersLoaded++;
                    if (layersLoaded === layerCount) {
                        ondone();
                    }
                };
            };

            for (var i = layerCount - 1; i >= 0; i--) {
                var layer = new Layer();
                var layerElement = layerElems[i];

                layer.name = layerElement.getAttribute('name');
                layer.x = layerElement.getAttribute('x') || 0;
                layer.y = layerElement.getAttribute('y') || 0;
                layer.composite = layerElement.getAttribute('composite-op') || 'svg:src-over';
                layer.opacity = layerElement.getAttribute('opacity') || 1;
                layer.visibility = layerElement.getAttribute('visibility') || 'visible';

                extractImage(layerElement.getAttribute('src'), fs, onExtract(layer));

                that.layers.push(layer);
            }
        }

        function loadStack(ondone) {
            var stackFile = fs.find('stack.xml');
            stackFile.getText(function (text) {
                var xml;
                // http://stackoverflow.com/questions/649614/xml-parsing-of-a-variable-string-in-javascript
                var parseXml;

                if (window.DOMParser) {
                    xml = ( new window.DOMParser() ).parseFromString(text, "text/xml");
                } else {
                    xml = new window.ActiveXObject("Microsoft.XMLDOM");
                    xml.async = false;
                    xml.loadXML(text);
                }

                var img = xml.getElementsByTagName('image')[0];
                that.width = img.getAttribute('w');
                that.height = img.getAttribute('h');

                loadLayers(img, ondone);
            });
        }

        function loadOra() {
            // keeping track of finished loading tasks
            var stepsDone = 0, steps = 3;
            var onDone = function () {
                stepsDone++;
                if (stepsDone === steps) {
                    onload();
                }
            };

            extractImage('Thumbnails/thumbnail.png', fs, function() {
                that.thumbnail = this;
                onDone();
            }, onDone);

            extractImage('mergedimage.png', fs, function() {
                that.prerendered = this;
                onDone();
            }, onDone);

            loadStack(onDone);
        }

        fs.importBlob(blob, loadOra);
    };

    OraFile.prototype.save = function(ondone) {
        zip.workerScriptsPath = obj.ora.scriptsPath;
        zip.useWebWorkers = obj.ora.enableWorkers;

        var fs = new zip.fs.FS(),
            thumbs = fs.root.addDirectory('Thumbnails'),
            data = fs.root.addDirectory('data'),
            tmpCanvas = document.createElement('canvas'),
            xmlDoc = document.implementation.createDocument(null, null, null),
            serializer = new XMLSerializer(),
            i = this.layers.length,
            layer, url, name, xelem, celem;

        fs.root.addText("mimetype", "image/openraster");

        xelem = xmlDoc.createElement('image');
        xelem.setAttribute('w', this.width);
        xelem.setAttribute('h', this.height);
        xmlDoc.appendChild(xelem);

        celem = xmlDoc.createElement('stack');
        xelem.appendChild(celem);
        xelem = celem;

        while(i) {
            layer = this.layers[i - 1];

            celem = xmlDoc.createElement('layer');
            celem.setAttribute('name', layer.name || ('layer' + i));
            celem.setAttribute('x', layer.x);
            celem.setAttribute('y', layer.y);
            celem.setAttribute('composite-op', layer.composite);
            celem.setAttribute('opacity', layer.opacity);
            celem.setAttribute('visibility', layer.visibility);

            url = layer.toCanvas(undefined, layer.width, layer.height, true).toDataURL('image/png');
            name = 'layer' + i + '.png';
            celem.setAttribute('src', 'data/' + name);
            data.addData64URI(name, url);
            xelem.appendChild(celem);
            i--;
        }

        fs.root.addText('stack.xml', serializer.serializeToString(xmlDoc));

        this.drawComposite(tmpCanvas, function() {
            url = tmpCanvas.toDataURL('image/png');
            fs.root.addData64URI('mergedimage.png', url);

            resize(tmpCanvas, 256, 256, function() {
                url = tmpCanvas.toDataURL('image/png');
                thumbs.addData64URI('thumbnail.png', url);

                fs.exportBlob(ondone, 'image/openraster');
            });
        });
    };

    // Draw the thumbnail into a canvas element
    OraFile.prototype.drawThumbnail = function (canvas) {
        var context = canvas.getContext('2d');

        if (this.thumbnail) {
            canvas.width = this.thumbnail.width;
            canvas.height = this.thumbnail.height;
            context.clearRect(0, 0, canvas.width, canvas.height);
            context.drawImage(this.thumbnail, 0, 0);
        }
        else {
            context.clearRect(0, 0, canvas.width, canvas.height);
        }
    };

    // Draw the full size composite image from the layer data.
    // Uses the prerendered image if present and enabled
    OraFile.prototype.drawComposite = function (canvas, ondone) {
        canvas.width = this.width;
        canvas.height = this.height;

        if(!this.layers) {
            if(ondone) {
                ondone();
            }

            return;
        }

        var context = canvas.getContext('2d');

        context.clearRect(0, 0, canvas.width, canvas.height);

        if(obj.ora.enablePrerendered && this.prerendered) {
            context.drawImage(this.prerendered, 0, 0);
            return;
        }

        if (!obj.blending) {
            composeNoBlend(this, context, ondone);
            return;
        }

        if(!obj.ora.enableWorkers || !window.Worker) {
            compose(this, context, ondone);
            return;
        }

        composeWorkers(this, context, ondone);
    };

    function composeWorkers (oraFile, context, ondone) {
        var layerCache = [],
            startLayer = -1,
            worker, i, tmpCanvas;

        for (i = 0; i < oraFile.layers.length; i++) {
            if(oraFile.layers[i].visibility != 'hidden') {
                if(startLayer < 0) {
                    startLayer = i;
                }

                layerCache[i] = {
                    opacity: oraFile.layers[i].opacity,
                    composite: oraFile.layers[i].composite,
                    data: oraFile.layers[i]
                        .toCanvas(tmpCanvas, oraFile.width, oraFile.height)
                        .getContext('2d')
                };
            }
        }

        if(startLayer < 0) {
            if(ondone) {
                ondone();
            }
            return;
        }

        function onTaskDone(e) {
            if(e.data.result) {
                // paint result
                context.putImageData(e.data.result, 0, 0);
                if(ondone) {
                    ondone();
                }
                worker.terminate();
                return;
            }

            var nextLayer = e.data.layer + 1;

            while(nextLayer < oraFile.layers.length && !layerCache[nextLayer]) {
                nextLayer++;
            }

            if(nextLayer >= oraFile.layers.length) {
                worker.postMessage({ done : true });
                return;
            }

            var nextBatch = {
                layer: nextLayer,
                src : layerCache[nextLayer].data.getImageData(0, 0, oraFile.width, oraFile.height),
                opacity: layerCache[nextLayer].opacity,
                filter: layerCache[nextLayer].composite
            };

            worker.postMessage(nextBatch);
        }

        worker = new Worker(obj.ora.scriptsPath + 'blender.js');
        worker.onmessage = onTaskDone;

        var initData = {
            layer: startLayer,
            src: layerCache[startLayer].data.getImageData(0, 0, oraFile.width, oraFile.height),
            opacity: layerCache[startLayer].opacity,
            filter: layerCache[startLayer].composite,
            dst: context.getImageData(0, 0, oraFile.width, oraFile.height)
        };

        worker.postMessage(initData);
    }

    function compose (oraFile, context, ondone) {
        var imgData = context.getImageData(0, 0, oraFile.width, oraFile.height),
            layerCount = oraFile.layers.length,
            layerIdx = 0,
            layer, tmpCanvas;

        while (layerCount > layerIdx) {
            layer = oraFile.layers[layerIdx];

            if (layer && layer.image && (layer.visibility === 'visible' || layer.visibility === undefined)) {
                var filter = obj.blending[layer.composite] || obj.blending.normal;
                var srcCanvas = layer.toCanvas(tmpCanvas, oraFile.width, oraFile.height);
                var src = srcCanvas.getContext('2d').getImageData(0, 0, srcCanvas.width, srcCanvas.height).data;
                obj.blending.blend(src, imgData.data, layer.opacity, filter);
            }

            layerIdx++;
        }

        context.putImageData(imgData, 0, 0);

        if(ondone) {
            ondone();
        }
    }

    function composeNoBlend(oraFile, context, ondone) {
        var layerCount = oraFile.layers.length,
            layerIdx = 0,
            layer;

        while (layerCount > layerIdx) {
            layer = oraFile.layers[layerIdx];
            if (layer && layer.image && (layer.visibility === 'visible' || layer.visibility === undefined)) {
                if (layer.opacity === undefined) {
                    context.globalAlpha = 1;
                } else {
                    context.globalAlpha = layer.opacity;
                }

                context.drawImage(layer.image, layer.x, layer.y);
            }

            layerIdx++;
        }

        if(ondone) {
            ondone();
        }
    }

    // Add a new layer to the image
    // index can optionally specify the position for the new layer
    OraFile.prototype.addLayer = function (name, index) {
        var layer = new Layer(this.width, this.height, name);
        if(index !== undefined && index < this.layers.length && index >= 0) {
            this.layers.splice(index, 0, layer);
        } else {
            this.layers.push(layer);
        }
        return layer;
    };

    // Create and populate an OraFile object from a blob
    // onload - callback with the loaded object as parameter
    function loadFile(blob, onload) {
        var oraFile = new OraFile();
        oraFile.load(blob, function() {
            onload(oraFile);
        });
    }

    obj.ora = {
        Ora : OraFile,
        OraLayer : Layer,
        load: loadFile,

        // enable use of prerendered image instead of layers (if present)
        enablePrerendered : true,
        enableWorkers : true,
        scriptsPath : ''
    };
})(this);<|endoftext|>