<commit_before>

DocumentVDOM.prototype.parseRelaxNG = function () {
	//check if it's a schema file
	//parse all global elements and create an ElementVDOM object
	var rootChildren = this.xmldoc.documentElement.childNodes;

	//do includes
	this.parseIncludes();

	debug(this.xmldoc.saveXML(this.xmldoc));
	for (var i = 0; i < rootChildren.length; i++) {
		if (rootChildren[i].isRelaxNGElement("start")) {
			this.parseStart(rootChildren[i]);
		}
	}
	return true;
}

DocumentVDOM.prototype.parseIncludes = function() {
	rootChild = this.xmldoc.documentElement.firstChild;
	var alreadyNext;
	while (rootChild) {
		alreadyNext = false;
		if (rootChild.isRelaxNGElement("include")) {
			var td = new mozileTransportDriver("http");
			debug (rootChild.getAttribute("href"));
			td.load(rootChild.getAttribute("href"),null, false);
			if (td.document.documentElement.isRelaxNGElement("grammar")) {
				var child = td.document.documentElement.firstChild;
				var insertionNode = rootChild.nextSibling;
				while (child) {
					var newChild = this.xmldoc.importNode(child,true);
					rootChild.parentNode.insertBefore(newChild,insertionNode);
					child = child.nextSibling;
				}
				var rootChildOld = rootChild;
				var rootChild = rootChild.nextSibling;
				alreadyNext = true;
				rootChildOld.parentNode.removeChild(rootChildOld);
			}
		}
		if (!alreadyNext) {
			rootChild = rootChild.nextSibling;
		}
	}

}

var rng_nsResolver;
DocumentVDOM.prototype.parseStart = function(node) {
	var startChildren = node.childNodes;
	rng_nsResolver = new bxe_RelaxNG_nsResolver(this.xmldoc.documentElement);

	for (var i = 0; i < startChildren.length; i++) {
		if (startChildren[i].isRelaxNGElement("element")) {
			var startElement = new ElementVDOM(startChildren[i]);
			this.firstChild = startElement;
			startElement.parentNode = this;
			var nsParts = rng_nsResolver.parseNodeNameOnElement(startChildren[i]);
			startElement.nodeName = nsParts.nodeName;
			startElement.localName = nsParts.localName;
			startElement.namespaceURI = nsParts.namespaceURI;
			startElement.prefix = nsParts.prefix;
			startElement.canBeRoot = true;
			startElement.nextSibling = null;
			startElement.previousSibling = null;

			break;
		}
	}

	startElement.parseChildren();
	dump("RelaxNG is parsed\n");

}

function bxe_RelaxNG_nsResolver(node) {
	var rootAttr = node.attributes;
	this.defaultNamespace = null;
	this.namespaces = new Array();
	for(var i = 0; i < rootAttr.length; i++) {
		if (rootAttr[i].namespaceURI == "http://www.w3.org/2000/xmlns/") {
			this.namespaces[rootAttr[i].localName] = rootAttr[i].value;
		} else if (rootAttr[i].localName == "ns") {
			this.defaultNamespace= rootAttr[i].value;
		}
	}
}
bxe_RelaxNG_nsResolver.prototype.lookupNamespaceURI = function(prefix) {
	if (this.namespaces[prefix]) {
		return this.namespaces[prefix];
	}
	return null;
}

bxe_RelaxNG_nsResolver.prototype.parseNodeNameOnElement = function(node) {
	var nodename = node.getAttribute("name");
	if (nodename) {
		return this.parseNodeName(nodename);
	}
	// no attribute name, search for childnode with name attribute
	var child = node.firstChild;
	var ret = new Object();
	while (child) {
		if (child.nodeType == 1 && child.localName == "name") {
			child.getAttribute("ns");
			ret.namespaceURI = child.getAttribute("ns");
			ret.localName = child.firstChild.data;
			ret.prefix = "";
			return ret;
		}
		child = child.nextSibling;
	}

}

bxe_RelaxNG_nsResolver.prototype.parseNodeName = function(nodename) {
	var spli = nodename.split(":");
	var ret = new Object;
	ret.nodeName = nodename;

	if (spli.length > 1) {
		ret.localName = spli[1];
		ret.namespaceURI = this.lookupNamespaceURI(spli[0]);
		ret.prefix = spli[0];
	} else {
		ret.localName = spli[0];
		ret.namespaceURI = this.defaultNamespace;
		ret.prefix = "";
	}
	return ret;

}

Node.prototype.__defineGetter__ ("hasRelaxNGNamespace", function() {

	if (this.namespaceURI == "http://relaxng.org/ns/structure/1.0") {
		return true;
	} else {
		return false;
	}
}
)
Node.prototype.isRelaxNGElement = function(nodename) {

//	dump ("isRelaxNGElement" + this.nodeType  +  " " + this.nodeName + " " + this.hasRelaxNGNamespace + "\n");
	if (this.nodeType == 1 && this.nodeName == nodename && this.hasRelaxNGNamespace) {
		return true;
	} else {
		return false;
	}
}

NodeVDOM.prototype.parseChildren = function(node) {
	var childNodes;

	if (node) {
		childNodes = node.childNodes;
	} else {
		childNodes = this.node.childNodes;
	}
	var newChoice;
	var newOneOrMore;

	for (var i = 0; i < childNodes.length; i++) {
		if (childNodes[i].isRelaxNGElement("element")) {
			var newElement = new ElementVDOM(childNodes[i]);
			var nsParts = rng_nsResolver.parseNodeNameOnElement(childNodes[i]);
			newElement.nodeName = nsParts.nodeName;
			newElement.localName = nsParts.localName;
			newElement.namespaceURI = nsParts.namespaceURI;
			newElement.prefix = nsParts.prefix;
			this.appendChild(newElement);
			newElement.parseChildren();

		} else if (childNodes[i].isRelaxNGElement("ref")) {
			//FIXME this can be done smarter... cache the defines.
			var grammarChild = this.node.ownerDocument.documentElement.childNodes;
			//dump ("ref: " + childNodes[i].getAttribute("name") +"\n");
			for (var j = 0; j < grammarChild.length; j++) {
				if (grammarChild[j].isRelaxNGElement("define") && grammarChild[j].getAttribute("name") == childNodes[i].getAttribute("name")) {
					//dump ("define" + grammarChild[j].getAttribute("name") +"\n");
					this.parseChildren(grammarChild[j]);
				}
			}
		}

		else if (childNodes[i].isRelaxNGElement("oneOrMore")) {
			newOneOrMore = new OneOrMoreVDOM(childNodes[i]);
			this.appendChild(newOneOrMore)
			newOneOrMore.parseChildren(childNodes[i]);

		} else if (childNodes[i].isRelaxNGElement("text")) {
			this.appendChild(new TextVDOM(childNodes[i]));

		} else if (childNodes[i].isRelaxNGElement("zeroOrMore")) {
			newOneOrMore = new OneOrMoreVDOM(childNodes[i]);
			this.appendChild(newOneOrMore);
			newOneOrMore.appendChild(new EmptyVDOM());
			newOneOrMore.parseChildren(childNodes[i]);

		} else if (childNodes[i].isRelaxNGElement("attribute")) {
			if (this.node.localName == "optional") {
				this.parentNode.addAttributeNode(new AttributeVDOM(childNodes[i]), "optional");
			} else {
				this.addAttributeNode(new AttributeVDOM(childNodes[i]), "optional");
			}
		} else if (childNodes[i].isRelaxNGElement("optional")) {
			newChoice = new ChoiceVDOM(childNodes[i]);
			this.appendChild(newChoice);
			newChoice.appendChild(new EmptyVDOM());
			newChoice.parseChildren();
		}
		else if (childNodes[i].isRelaxNGElement("choice")) {
			newChoice = new ChoiceVDOM(childNodes[i]);
			this.appendChild(newChoice);
			newChoice.parseChildren();
		}
	}
}

ChoiceVDOM.prototype = new NodeVDOM();

ChoiceVDOM.prototype.isValid = function(ctxt) {
	var child = this.firstChild;
	//dump ("Choice.isValid: " + this.nodeName+"\n");
	var hasEmpty = false;
	while (child) {
		//dump ("Choice.child.isValid: " + child.nodeName + "\n");
		if (child.type == "RELAXNG_EMPTY") {
			hasEmpty = true;
		}
		if (child.isValid(ctxt)) {
			ctxt.vdom = this;
			return true;
		}
		child= child.nextSibling;
	}
	if (hasEmpty) {
		var vdom = ctxt.nextVDOM();
		if (vdom) {
			return vdom.isValid(ctxt);
		}
	}
	return false;
}

function ChoiceVDOM(node) {
	this.node = node;
	this.type = "RELAXNG_CHOICE";
	this.nodeName = "RELAXNG_CHOICE";
	this.attributes = new Array();
}
EmptyVDOM.prototype = new NodeVDOM();

function EmptyVDOM(node) {
	this.node = node;
	this.type = "RELAXNG_EMPTY";
	this.nodeName = "RELAXNG_EMPTY";
}

EmptyVDOM.prototype.isValid  = function() {
	return false;
}
TextVDOM.prototype = new NodeVDOM();

function TextVDOM(node ) {
	this.node = node;
	this.type = "RELAXNG_TEXT";
	this.nodeName = "RELAXNG_TEXT";
}

TextVDOM.prototype.isValid = function(ctxt) {
	//dump("TextVDOM.isValid :" + ctxt.node.data + ":\n");
	if (ctxt.node.nodeType == 3) {
		return true;
	} else {
		return false;
	}

}

OneOrMoreVDOM.prototype = new NodeVDOM();

function OneOrMoreVDOM(node) {
	this.type = "RELAXNG_ONEORMORE";
	this.nodeName = "RELAXNG_ONEORMORE";
	this.node = node;
	this.hit = false;
}

OneOrMoreVDOM.prototype.isValid = function(ctxt) {
	var child = this.firstChild;
	//dump ("OneorMore.isValid:\n");
	while (child) {
		//dump ("OneorMore.child.isValid: " + child.nodeName + "\n");
		if (child.isValid(ctxt)) {
			ctxt.vdom = this;
			this.hit = true;
			return true;
		}
		child = child.nextSibling;
	}
	if (this.hit) {
		var vdom = ctxt.nextVDOM();
		if (vdom) {
			return vdom.isValid(ctxt);
		}
	}
	return false;
}

OneOrMoreVDOM.prototype.allowedElements = function() {
	var child = this.firstChild;
	var ac = new Array();

	while (child) {
		var subac = child.allowedElements();
		if (subac.nodeName) {
			ac.push(subac);
		} else {
			for (var i = 0; i < subac.length; i++) {
				ac.push(subac[i]);
			}
		}
		child = child.nextSibling;
	}
	return ac;

}

ChoiceVDOM.prototype.allowedElements = function() {
	var child = this.firstChild;
	var ac = new Array();

	while (child) {
		var subac = child.allowedElements();
		if (subac && subac.nodeName) {
			ac.push(subac);
		} else if (subac) {
			for (var i = 0; i < subac.length; i++) {
				ac.push(subac[i]);
			}
		}
		child = child.nextSibling;
	}

	return ac;

}

ElementVDOM.prototype.allowedElements = function() {
	var nodeName = "" ;
	if (this.prefix) {
		nodeName = this.prefix +":";
	}
	return {"nodeName":nodeName + this.localName, "namespaceURI": this.namespaceURI, "localName": this.localName};
}

ElementVDOM.prototype.__defineSetter__("nodeName", function(name) {
	var html = true;
	if (html) {
		this._xmlnodeName = name;
	}
}
)

ElementVDOM.prototype.__defineGetter__("nodeName", function(name) {
	return this._xmlnodeName;
}
)

DocumentVDOM.prototype.getStructure = function() {

	 return "\n"+ this.firstChild.getStructure();
}

NodeVDOM.prototype.getStructure = function(level) {
	var out = this.nodeName + " " + this.minOccurs + " " + this.maxOccurs + "\n";
	var child = this.firstChild;
	if (!level ) {
		level = 0;
	}
	var indent = "";
	for (var i = 0; i <= level; i++) {
		indent += "  ";
	}
	for ( i in this.attributes) {
		out += indent + "@" + i + " " + this.attributes[i].dataType + "\n";

	}
	if ( typeof child != "undefined") {
		while ( child != null && child != "undefined") {

			//out += indent + child.nodeName + "\n";

			out += indent + child.getStructure(level + 1);
			child = child.nextSibling;
		}

	}
	return out;
}

XMLNode.prototype.__defineGetter__(
"isWhitespaceOnly",
function()
{
	if (this.nodeType == 3) {
		if(/\S+/.test(this._node.nodeValue)) // any non white space visible characters
			return false;

		return true;
	} else {
		return false;
	}
}
);

<commit_msg>give feedback about loading<commit_after>

DocumentVDOM.prototype.parseRelaxNG = function () {

	//do includes
	this.parseIncludes();

	var rootChildren = this.xmldoc.documentElement.childNodes;

	for (var i = 0; i < rootChildren.length; i++) {
		if (rootChildren[i].isRelaxNGElement("start")) {
			this.parseStart(rootChildren[i]);
		}
	}
	return true;
}

DocumentVDOM.prototype.parseIncludes = function() {
	var rootChild = this.xmldoc.documentElement.firstChild;
	var alreadyNext;
	while (rootChild) {
		alreadyNext = false;
		if (rootChild.isRelaxNGElement("include")) {
			var td = new mozileTransportDriver("http");
			debug (rootChild.getAttribute("href"));
			bxe_about_box.addText(rootChild.getAttribute("href") + " " );
			td.load(rootChild.getAttribute("href"), null, false);

			if (td.document.documentElement.isRelaxNGElement("grammar")) {
				var child = td.document.documentElement.firstChild;
				var insertionNode = rootChild.nextSibling;
				while (child) {
					var newChild = this.xmldoc.importNode(child,true);
					rootChild.parentNode.insertBefore(newChild,insertionNode);
					child = child.nextSibling;
				}
				var rootChildOld = rootChild;
				var rootChild = rootChild.nextSibling;
				alreadyNext = true;
				rootChildOld.parentNode.removeChild(rootChildOld);
			}
		}
		if (!alreadyNext) {
			rootChild = rootChild.nextSibling;
		}
	}

}

var rng_nsResolver;
DocumentVDOM.prototype.parseStart = function(node) {
	var startChildren = node.childNodes;
	rng_nsResolver = new bxe_RelaxNG_nsResolver(this.xmldoc.documentElement);

	for (var i = 0; i < startChildren.length; i++) {
		if (startChildren[i].isRelaxNGElement("element")) {
			var startElement = new ElementVDOM(startChildren[i]);
			this.firstChild = startElement;
			startElement.parentNode = this;
			var nsParts = rng_nsResolver.parseNodeNameOnElement(startChildren[i]);
			startElement.nodeName = nsParts.nodeName;
			startElement.localName = nsParts.localName;
			startElement.namespaceURI = nsParts.namespaceURI;
			startElement.prefix = nsParts.prefix;
			startElement.canBeRoot = true;
			startElement.nextSibling = null;
			startElement.previousSibling = null;

			break;
		}
	}

	startElement.parseChildren();
	dump("RelaxNG is parsed\n");

}

function bxe_RelaxNG_nsResolver(node) {
	var rootAttr = node.attributes;
	this.defaultNamespace = null;
	this.namespaces = new Array();
	for(var i = 0; i < rootAttr.length; i++) {
		if (rootAttr[i].namespaceURI == "http://www.w3.org/2000/xmlns/") {
			this.namespaces[rootAttr[i].localName] = rootAttr[i].value;
		} else if (rootAttr[i].localName == "ns") {
			this.defaultNamespace= rootAttr[i].value;
		}
	}
}
bxe_RelaxNG_nsResolver.prototype.lookupNamespaceURI = function(prefix) {
	if (this.namespaces[prefix]) {
		return this.namespaces[prefix];
	}
	return null;
}

bxe_RelaxNG_nsResolver.prototype.parseNodeNameOnElement = function(node) {
	var nodename = node.getAttribute("name");
	if (nodename) {
		return this.parseNodeName(nodename);
	}
	// no attribute name, search for childnode with name attribute
	var child = node.firstChild;
	var ret = new Object();
	while (child) {
		if (child.nodeType == 1 && child.localName == "name") {
			child.getAttribute("ns");
			ret.namespaceURI = child.getAttribute("ns");
			ret.localName = child.firstChild.data;
			ret.prefix = "";
			return ret;
		}
		child = child.nextSibling;
	}

}

bxe_RelaxNG_nsResolver.prototype.parseNodeName = function(nodename) {
	var spli = nodename.split(":");
	var ret = new Object;
	ret.nodeName = nodename;

	if (spli.length > 1) {
		ret.localName = spli[1];
		ret.namespaceURI = this.lookupNamespaceURI(spli[0]);
		ret.prefix = spli[0];
	} else {
		ret.localName = spli[0];
		ret.namespaceURI = this.defaultNamespace;
		ret.prefix = "";
	}
	return ret;

}

Node.prototype.__defineGetter__ ("hasRelaxNGNamespace", function() {

	if (this.namespaceURI == "http://relaxng.org/ns/structure/1.0") {
		return true;
	} else {
		return false;
	}
}
)
Node.prototype.isRelaxNGElement = function(nodename) {

//	dump ("isRelaxNGElement" + this.nodeType  +  " " + this.nodeName + " " + this.hasRelaxNGNamespace + "\n");
	if (this.nodeType == 1 && this.nodeName == nodename && this.hasRelaxNGNamespace) {
		return true;
	} else {
		return false;
	}
}

NodeVDOM.prototype.parseChildren = function(node) {
	var childNodes;

	if (node) {
		childNodes = node.childNodes;
	} else {
		childNodes = this.node.childNodes;
	}
	var newChoice;
	var newOneOrMore;

	for (var i = 0; i < childNodes.length; i++) {
		if (childNodes[i].isRelaxNGElement("element")) {
			var newElement = new ElementVDOM(childNodes[i]);
			var nsParts = rng_nsResolver.parseNodeNameOnElement(childNodes[i]);
			newElement.nodeName = nsParts.nodeName;
			newElement.localName = nsParts.localName;
			newElement.namespaceURI = nsParts.namespaceURI;
			newElement.prefix = nsParts.prefix;
			this.appendChild(newElement);
			newElement.parseChildren();

		} else if (childNodes[i].isRelaxNGElement("ref")) {
			//FIXME this can be done smarter... cache the defines.
			var grammarChild = this.node.ownerDocument.documentElement.childNodes;
			//dump ("ref: " + childNodes[i].getAttribute("name") +"\n");
			for (var j = 0; j < grammarChild.length; j++) {
				if (grammarChild[j].isRelaxNGElement("define") && grammarChild[j].getAttribute("name") == childNodes[i].getAttribute("name")) {
					//dump ("define" + grammarChild[j].getAttribute("name") +"\n");
					this.parseChildren(grammarChild[j]);
				}
			}
		}

		else if (childNodes[i].isRelaxNGElement("oneOrMore")) {
			newOneOrMore = new OneOrMoreVDOM(childNodes[i]);
			this.appendChild(newOneOrMore)
			newOneOrMore.parseChildren(childNodes[i]);

		} else if (childNodes[i].isRelaxNGElement("text")) {
			this.appendChild(new TextVDOM(childNodes[i]));

		} else if (childNodes[i].isRelaxNGElement("zeroOrMore")) {
			newOneOrMore = new OneOrMoreVDOM(childNodes[i]);
			this.appendChild(newOneOrMore);
			newOneOrMore.appendChild(new EmptyVDOM());
			newOneOrMore.parseChildren(childNodes[i]);

		} else if (childNodes[i].isRelaxNGElement("attribute")) {
			if (this.node.localName == "optional") {
				this.parentNode.addAttributeNode(new AttributeVDOM(childNodes[i]), "optional");
			} else {
				this.addAttributeNode(new AttributeVDOM(childNodes[i]), "optional");
			}
		} else if (childNodes[i].isRelaxNGElement("optional")) {
			newChoice = new ChoiceVDOM(childNodes[i]);
			this.appendChild(newChoice);
			newChoice.appendChild(new EmptyVDOM());
			newChoice.parseChildren();
		}
		else if (childNodes[i].isRelaxNGElement("choice")) {
			newChoice = new ChoiceVDOM(childNodes[i]);
			this.appendChild(newChoice);
			newChoice.parseChildren();
		}
	}
}

ChoiceVDOM.prototype = new NodeVDOM();

ChoiceVDOM.prototype.isValid = function(ctxt) {
	var child = this.firstChild;
	//dump ("Choice.isValid: " + this.nodeName+"\n");
	var hasEmpty = false;
	while (child) {
		//dump ("Choice.child.isValid: " + child.nodeName + "\n");
		if (child.type == "RELAXNG_EMPTY") {
			hasEmpty = true;
		}
		if (child.isValid(ctxt)) {
			ctxt.vdom = this;
			return true;
		}
		child= child.nextSibling;
	}
	if (hasEmpty) {
		var vdom = ctxt.nextVDOM();
		if (vdom) {
			return vdom.isValid(ctxt);
		}
	}
	return false;
}

function ChoiceVDOM(node) {
	this.node = node;
	this.type = "RELAXNG_CHOICE";
	this.nodeName = "RELAXNG_CHOICE";
	this.attributes = new Array();
}
EmptyVDOM.prototype = new NodeVDOM();

function EmptyVDOM(node) {
	this.node = node;
	this.type = "RELAXNG_EMPTY";
	this.nodeName = "RELAXNG_EMPTY";
}

EmptyVDOM.prototype.isValid  = function() {
	return false;
}
TextVDOM.prototype = new NodeVDOM();

function TextVDOM(node ) {
	this.node = node;
	this.type = "RELAXNG_TEXT";
	this.nodeName = "RELAXNG_TEXT";
}

TextVDOM.prototype.isValid = function(ctxt) {
	//dump("TextVDOM.isValid :" + ctxt.node.data + ":\n");
	if (ctxt.node.nodeType == 3) {
		return true;
	} else {
		return false;
	}

}

OneOrMoreVDOM.prototype = new NodeVDOM();

function OneOrMoreVDOM(node) {
	this.type = "RELAXNG_ONEORMORE";
	this.nodeName = "RELAXNG_ONEORMORE";
	this.node = node;
	this.hit = false;
}

OneOrMoreVDOM.prototype.isValid = function(ctxt) {
	var child = this.firstChild;
	//dump ("OneorMore.isValid:\n");
	while (child) {
		//dump ("OneorMore.child.isValid: " + child.nodeName + "\n");
		if (child.isValid(ctxt)) {
			ctxt.vdom = this;
			this.hit = true;
			return true;
		}
		child = child.nextSibling;
	}
	if (this.hit) {
		var vdom = ctxt.nextVDOM();
		if (vdom) {
			return vdom.isValid(ctxt);
		}
	}
	return false;
}

OneOrMoreVDOM.prototype.allowedElements = function() {
	var child = this.firstChild;
	var ac = new Array();

	while (child) {
		var subac = child.allowedElements();
		if (subac.nodeName) {
			ac.push(subac);
		} else {
			for (var i = 0; i < subac.length; i++) {
				ac.push(subac[i]);
			}
		}
		child = child.nextSibling;
	}
	return ac;

}

ChoiceVDOM.prototype.allowedElements = function() {
	var child = this.firstChild;
	var ac = new Array();

	while (child) {
		var subac = child.allowedElements();
		if (subac && subac.nodeName) {
			ac.push(subac);
		} else if (subac) {
			for (var i = 0; i < subac.length; i++) {
				ac.push(subac[i]);
			}
		}
		child = child.nextSibling;
	}

	return ac;

}

ElementVDOM.prototype.allowedElements = function() {
	var nodeName = "" ;
	if (this.prefix) {
		nodeName = this.prefix +":";
	}
	return {"nodeName":nodeName + this.localName, "namespaceURI": this.namespaceURI, "localName": this.localName};
}

ElementVDOM.prototype.__defineSetter__("nodeName", function(name) {
	var html = true;
	if (html) {
		this._xmlnodeName = name;
	}
}
)

ElementVDOM.prototype.__defineGetter__("nodeName", function(name) {
	return this._xmlnodeName;
}
)

DocumentVDOM.prototype.getStructure = function() {

	 return "\n"+ this.firstChild.getStructure();
}

NodeVDOM.prototype.getStructure = function(level) {
	var out = this.nodeName + " " + this.minOccurs + " " + this.maxOccurs + "\n";
	var child = this.firstChild;
	if (!level ) {
		level = 0;
	}
	var indent = "";
	for (var i = 0; i <= level; i++) {
		indent += "  ";
	}
	for ( i in this.attributes) {
		out += indent + "@" + i + " " + this.attributes[i].dataType + "\n";

	}
	if ( typeof child != "undefined") {
		while ( child != null && child != "undefined") {

			//out += indent + child.nodeName + "\n";

			out += indent + child.getStructure(level + 1);
			child = child.nextSibling;
		}

	}
	return out;
}

XMLNode.prototype.__defineGetter__(
"isWhitespaceOnly",
function()
{
	if (this.nodeType == 3) {
		if(/\S+/.test(this._node.nodeValue)) // any non white space visible characters
			return false;

		return true;
	} else {
		return false;
	}
}
);

<|endoftext|>


<commit_before>$(function() {
    var category =1;
    var app = {
        init: function() {
            this.user = {};
            $('.menu-crud').addClass('hidden');
            $('.menu-user').addClass('hidden');
            $('.menu-loading').removeClass('hidden');
            $('.menu-user').addClass('hidden');
            $('.btn-login').addClass('hidden');
            $('.searchbar').addClass('hidden');
            $('.btn-login').attr('href', '/api/login?url=/');
            $('.btn-logout').attr('href','/api/logout?url=/');
            this.router = new Router();
            this.setEventListeners();
            this.getUser();
            Backbone.history.start({pushState: true});
        },
        setEventListeners: function() {
            var self = this;
            $('.menu-list').popover({ trigger: "hover" });
            $('.menu-create').popover({ trigger: "hover" });
            $('.menu-search').popover({ trigger: "hover" });
            $('.menu-mylist').popover({ trigger: "hover" });
            $('.menu-crud .item a').click(function(ev) {
                var $el = $(ev.target).closest('.item');
                $('.menu-crud .item').removeClass('active');
                $el.addClass("active");
                if ($el.hasClass('menu-list')) {
                    $('.searchbar').addClass('hidden');
                    category=1;
                    self.router.navigate('list', {trigger: true});
                }
                if ($el.hasClass('menu-create')) {
                    $('.searchbar').addClass('hidden');
                    self.router.navigate('new', {trigger: true});
                }
                if ($el.hasClass('menu-search')) {
                    $('.searchbar').removeClass('hidden');
                    $('.app-content').html('');
                    self.router.navigate('search?=', {trigger: true});
                }
                if ($el.hasClass('menu-mylist')) {
                    $('.searchbar').addClass('hidden');
                    category=2;
                    self.router.navigate('list2', {trigger: true});
                }
            });
            $('.form-search').unbind('submit').submit(function(ev) {
                self.router.navigate('search?=' + $('.search-input').val(), {trigger: true});
                return false;
            });
        },
        getUser: function() {
            var self = this;
            $.ajax({
                method: 'GET',
                url: '/api/users/me',
                success: function(me) {
                    self.user = me;
                    self.showLogout();
                },
                error: function(err) {
                    self.showLogin();
                }
            });
        },
        showLogin: function() {
           $('.menu-loading').addClass('hidden');
           $('.menu-user').addClass('hidden');
           $('.btn-login').removeClass('hidden');
        },
        showLogout: function() {
           var slice = 0;
           for (i=0;i<this.user.email.length;i++){
               if (this.user.email[i] == '@'){
                  slice = i
               }
           }
           $('.user-email').text(this.user.email.slice(0,slice));
           $('.menu-crud').removeClass('hidden');
           $('.menu-loading').addClass('hidden');
           $('.btn-login').addClass('hidden');
           $('.menu-user').removeClass('hidden');
           $('.menu-list').addClass("active");
           this.router.navigate('list', {trigger: true});
        },
        showList: function() {
            if (category === 1){
                var $listTemplate = getTemplate('tpl-thesis-list');
                $('.app-content').html($listTemplate);
            } else {
                var $listTemplate = getTemplate('tpl-thesis-list2');
                $('.app-content').html($listTemplate);
            }
        },
        search: function(query, callback) {
            $.get('/api/search/?q=' + query, {
                returned_fields: JSON.stringify(['Title', 'Year'])
            }, function(list) {
                callback(list);
            });
        },
        getThesisByID: function(id, callback) {
            var object = {};
            $.get('/api/thesis/' + id, function(item) {
                callback(item);
            });
        },
        showThesis: function(thesis) {
            var self = this;
            var $viewTemplate = getTemplate('tpl-thesis-detail', thesis);
            $('.app-content').html($viewTemplate);
        },
        showForm: function(object) {
            var self = this;
            if (!object) {
                object = {};
            }
            var $formTemplate = getTemplate('tpl-thesis-form', object);
            $('.app-content').html($formTemplate);
            $('form').unbind('submit').submit(function(ev) {
                var thesisObject = {};
                var inputs = $('form').serializeArray();
                for (var i = 0; i < inputs.length; i++) {
                    thesisObject[inputs[i].name] = inputs[i].value;
                }
                self.save(thesisObject);
                return false;
            });
            $('.btn-cancel').click(function() {
                self.router.navigate('list', {trigger: true});
                $('.menu-list').addClass("active");
                return false;
            });
            self.setEventListeners();
        },
        loadAllThesis: function() {
            var self = this;
            setTimeout(function() {
                $.get('/api/thesis', function(res) {
                    self.displayLoadedList(res);
                });
            }, 200);
        },
        displayLoadedList: function(list) {
            var self = this;
            if (list.length === 0) {
                $('.app-content').html("There are no thesis files at the moment.");
            } else {

                _.each(list, function(item) {
                    if (category === 1){
                        var $thesisItem = $(getTemplate('tpl-thesis-list-item', item));
                    } else {
                        if (item.owners[0] == self.user.Id){
                            var $thesisItem = $(getTemplate('tpl-thesis-list-item2', item));
                        }
                    }
                    var id = item.Id
                    if (item.Key) {
                        id = item.Key;
                    }
                    $thesisItem.find('.btn-edit').click(function() {
                        self.router.navigate('edit-' + id, {trigger: true});
                    });
                    $thesisItem.find('.btn-delete').click(function() {
                        self.router.navigate('delete-' + id, {trigger: true});
                    });
                    $('.thesis-list').append($thesisItem);
                });
            }
        },
        save: function(object) {
            var self = this;

            var thesisObject = {};
            var inputs = $('form').serializeArray();
            for (var i = 0; i < inputs.length; i++)
            {
                thesisObject[inputs[i].name] = inputs[i].value;
            }
            if (thesisObject.Title.length != 0 && thesisObject.Description.length != 0)
            {
                $.post('api/thesis', object, function(res) {
                self.router.navigate('list', {trigger: true});
                $('.menu-list').addClass("active");
                $('.menu-create').removeClass("active");
                $('.col-md-7').html('A thesis was created/modified.'+$('.col-md-7').html());
                });
                return false;
            }
            else
            {
                $('#new-thesis-form').html($('#new-thesis-form').html()+ 'Please fill up the required fields(*).');
            }
        },
        deleteThesis: function(id){
            var self = this;
            $.ajax({
                type: 'DELETE',
                url: '/api/thesis/' + id,
                success: function() {
                    self.router.navigate('list', {trigger: true});
                }
            });
        },
        searchThesis: function(keyword){
            var self = this;
            var regexStatement = new RegExp(".*(" + keyword + ").*", "i");
            $.get('/api/thesis', function(obj){
               var sorted_list = $.grep(obj, function(thesis, index){
                    return regexStatement.test(thesis.Title);
               });
               if(sorted_list.length == 0){
                    $('.app-content').html('Your search does not have any match.');
               }
               else{
                    var $listTemplate = getTemplate('tpl-thesis-list');
                    $('.app-content').html($listTemplate);
                    _.each(sorted_list, function(item) {
                        var $thesisItem = $(getTemplate('tpl-thesis-list-item', item));
                        $('.thesis-list').append($thesisItem);
                        var id = item.Id
                        if (item.Key) {
                            id = item.Key;
                        }
                        $thesisItem.find('.btn-edit').click(function() {
                            self.router.navigate('edit-' + id, {trigger: true});
                        });
                        $thesisItem.find('.btn-view').click(function() {
                            self.router.navigate('thesis-' + id, {trigger: true});
                        });
                        $thesisItem.find('.btn-delete').click(function() {
                            self.router.navigate('delete-' + id, {trigger: true});
                        });
                    });
               }
               $('.search-input').val('');
            });
        }
    };
    function getTemplate(template_id, context) {
        var template, $template, markup;
        template = $('#' + template_id);
        $template = Handlebars.compile(template.html());
        markup = $template(context);
        return markup;
    }
    var Router = Backbone.Router.extend({
        routes: {
            '': 'onHome',
            'search?=:query': 'onSearch',
            'thesis-:id': 'onView',
            'new': 'onCreate',
            'edit-:id': 'onEdit',
            'list': 'onList',
            'list2': 'onList',
            'delete-:id': 'onDelete'
       },
       onSearch: function(query) {
            app.searchThesis(query);
       },
       onView: function(id) {
            $('.menu-list').removeClass("active");
            app.getThesisByID(id, function(item) {
                app.showThesis(item);
                FB.XFBML.parse();
            });
       },
       onCreate: function() {
            app.showForm();
       },
       onEdit: function(id) {
            $('.menu-list').removeClass("active");
            app.getThesisByID(id, function(item) {
                app.showForm(item);
            });
       },
       onList: function() {
            app.showList();
            app.loadAllThesis();
       },
       onDelete: function(id) {
            app.deleteThesis(id);
       },

    });
    app.init();
});
<commit_msg>return the line of view thesis<commit_after>$(function() {
    var category =1;
    var app = {
        init: function() {
            this.user = {};
            $('.menu-crud').addClass('hidden');
            $('.menu-user').addClass('hidden');
            $('.menu-loading').removeClass('hidden');
            $('.menu-user').addClass('hidden');
            $('.btn-login').addClass('hidden');
            $('.searchbar').addClass('hidden');
            $('.btn-login').attr('href', '/api/login?url=/');
            $('.btn-logout').attr('href','/api/logout?url=/');
            this.router = new Router();
            this.setEventListeners();
            this.getUser();
            Backbone.history.start({pushState: true});
        },
        setEventListeners: function() {
            var self = this;
            $('.menu-list').popover({ trigger: "hover" });
            $('.menu-create').popover({ trigger: "hover" });
            $('.menu-search').popover({ trigger: "hover" });
            $('.menu-mylist').popover({ trigger: "hover" });
            $('.menu-crud .item a').click(function(ev) {
                var $el = $(ev.target).closest('.item');
                $('.menu-crud .item').removeClass('active');
                $el.addClass("active");
                if ($el.hasClass('menu-list')) {
                    $('.searchbar').addClass('hidden');
                    category=1;
                    self.router.navigate('list', {trigger: true});
                }
                if ($el.hasClass('menu-create')) {
                    $('.searchbar').addClass('hidden');
                    self.router.navigate('new', {trigger: true});
                }
                if ($el.hasClass('menu-search')) {
                    $('.searchbar').removeClass('hidden');
                    $('.app-content').html('');
                    self.router.navigate('search?=', {trigger: true});
                }
                if ($el.hasClass('menu-mylist')) {
                    $('.searchbar').addClass('hidden');
                    category=2;
                    self.router.navigate('list2', {trigger: true});
                }
            });
            $('.form-search').unbind('submit').submit(function(ev) {
                self.router.navigate('search?=' + $('.search-input').val(), {trigger: true});
                return false;
            });
        },
        getUser: function() {
            var self = this;
            $.ajax({
                method: 'GET',
                url: '/api/users/me',
                success: function(me) {
                    self.user = me;
                    self.showLogout();
                },
                error: function(err) {
                    self.showLogin();
                }
            });
        },
        showLogin: function() {
           $('.menu-loading').addClass('hidden');
           $('.menu-user').addClass('hidden');
           $('.btn-login').removeClass('hidden');
        },
        showLogout: function() {
           var slice = 0;
           for (i=0;i<this.user.email.length;i++){
               if (this.user.email[i] == '@'){
                  slice = i
               }
           }
           $('.user-email').text(this.user.email.slice(0,slice));
           $('.menu-crud').removeClass('hidden');
           $('.menu-loading').addClass('hidden');
           $('.btn-login').addClass('hidden');
           $('.menu-user').removeClass('hidden');
           $('.menu-list').addClass("active");
           this.router.navigate('list', {trigger: true});
        },
        showList: function() {
            if (category === 1){
                var $listTemplate = getTemplate('tpl-thesis-list');
                $('.app-content').html($listTemplate);
            } else {
                var $listTemplate = getTemplate('tpl-thesis-list2');
                $('.app-content').html($listTemplate);
            }
        },
        search: function(query, callback) {
            $.get('/api/search/?q=' + query, {
                returned_fields: JSON.stringify(['Title', 'Year'])
            }, function(list) {
                callback(list);
            });
        },
        getThesisByID: function(id, callback) {
            var object = {};
            $.get('/api/thesis/' + id, function(item) {
                callback(item);
            });
        },
        showThesis: function(thesis) {
            var self = this;
            var $viewTemplate = getTemplate('tpl-thesis-detail', thesis);
            $('.app-content').html($viewTemplate);
        },
        showForm: function(object) {
            var self = this;
            if (!object) {
                object = {};
            }
            var $formTemplate = getTemplate('tpl-thesis-form', object);
            $('.app-content').html($formTemplate);
            $('form').unbind('submit').submit(function(ev) {
                var thesisObject = {};
                var inputs = $('form').serializeArray();
                for (var i = 0; i < inputs.length; i++) {
                    thesisObject[inputs[i].name] = inputs[i].value;
                }
                self.save(thesisObject);
                return false;
            });
            $('.btn-cancel').click(function() {
                self.router.navigate('list', {trigger: true});
                $('.menu-list').addClass("active");
                return false;
            });
            self.setEventListeners();
        },
        loadAllThesis: function() {
            var self = this;
            setTimeout(function() {
                $.get('/api/thesis', function(res) {
                    self.displayLoadedList(res);
                });
            }, 200);
        },
        displayLoadedList: function(list) {
            var self = this;
            if (list.length === 0) {
                $('.app-content').html("There are no thesis files at the moment.");
            } else {

                _.each(list, function(item) {
                    if (category === 1){
                        var $thesisItem = $(getTemplate('tpl-thesis-list-item', item));
                    } else {
                        if (item.owners[0] == self.user.Id){
                            var $thesisItem = $(getTemplate('tpl-thesis-list-item2', item));
                        }
                    }
                    var id = item.Id
                    if (item.Key) {
                        id = item.Key;
                    }
                    $thesisItem.find('.btn-edit').click(function() {
                        self.router.navigate('edit-' + id, {trigger: true});
                    });
                    $thesisItem.find('.btn-view').click(function() {
                        self.router.navigate('thesis-' + id, {trigger: true});
                    });
                    $thesisItem.find('.btn-delete').click(function() {
                        self.router.navigate('delete-' + id, {trigger: true});
                    });
                    $('.thesis-list').append($thesisItem);
                });
            }
        },
        save: function(object) {
            var self = this;

            var thesisObject = {};
            var inputs = $('form').serializeArray();
            for (var i = 0; i < inputs.length; i++)
            {
                thesisObject[inputs[i].name] = inputs[i].value;
            }
            if (thesisObject.Title.length != 0 && thesisObject.Description.length != 0)
            {
                $.post('api/thesis', object, function(res) {
                self.router.navigate('list', {trigger: true});
                $('.menu-list').addClass("active");
                $('.menu-create').removeClass("active");
                $('.col-md-7').html('A thesis was created/modified.'+$('.col-md-7').html());
                });
                return false;
            }
            else
            {
                $('#new-thesis-form').html($('#new-thesis-form').html()+ 'Please fill up the required fields(*).');
            }
        },
        deleteThesis: function(id){
            var self = this;
            $.ajax({
                type: 'DELETE',
                url: '/api/thesis/' + id,
                success: function() {
                    self.router.navigate('list', {trigger: true});
                }
            });
        },
        searchThesis: function(keyword){
            var self = this;
            var regexStatement = new RegExp(".*(" + keyword + ").*", "i");
            $.get('/api/thesis', function(obj){
               var sorted_list = $.grep(obj, function(thesis, index){
                    return regexStatement.test(thesis.Title);
               });
               if(sorted_list.length == 0){
                    $('.app-content').html('Your search does not have any match.');
               }
               else{
                    var $listTemplate = getTemplate('tpl-thesis-list');
                    $('.app-content').html($listTemplate);
                    _.each(sorted_list, function(item) {
                        var $thesisItem = $(getTemplate('tpl-thesis-list-item', item));
                        $('.thesis-list').append($thesisItem);
                        var id = item.Id
                        if (item.Key) {
                            id = item.Key;
                        }
                        $thesisItem.find('.btn-edit').click(function() {
                            self.router.navigate('edit-' + id, {trigger: true});
                        });
                        $thesisItem.find('.btn-view').click(function() {
                            self.router.navigate('thesis-' + id, {trigger: true});
                        });
                        $thesisItem.find('.btn-delete').click(function() {
                            self.router.navigate('delete-' + id, {trigger: true});
                        });
                    });
               }
               $('.search-input').val('');
            });
        }
    };
    function getTemplate(template_id, context) {
        var template, $template, markup;
        template = $('#' + template_id);
        $template = Handlebars.compile(template.html());
        markup = $template(context);
        return markup;
    }
    var Router = Backbone.Router.extend({
        routes: {
            '': 'onHome',
            'search?=:query': 'onSearch',
            'thesis-:id': 'onView',
            'new': 'onCreate',
            'edit-:id': 'onEdit',
            'list': 'onList',
            'list2': 'onList',
            'delete-:id': 'onDelete'
       },
       onSearch: function(query) {
            app.searchThesis(query);
       },
       onView: function(id) {
            $('.menu-list').removeClass("active");
            app.getThesisByID(id, function(item) {
                app.showThesis(item);
                FB.XFBML.parse();
            });
       },
       onCreate: function() {
            app.showForm();
       },
       onEdit: function(id) {
            $('.menu-list').removeClass("active");
            app.getThesisByID(id, function(item) {
                app.showForm(item);
            });
       },
       onList: function() {
            app.showList();
            app.loadAllThesis();
       },
       onDelete: function(id) {
            app.deleteThesis(id);
       },

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


<commit_before>Template.registerHelper("activeRoute", function(route) {
	var currentRoute = Router.current().route.getName();
	return currentRoute === route ? " active" : "";
});

Template.registerHelper("calendarTime", function(date) {
	if(date) {
		return moment(date).calendar();
	}
	return "Unknown";
});

Template.registerHelper("equals", function(a, b) {
	return a === b;
});

Template.navigation.events({
	"click #logout": function(e) {
		Meteor.logout(function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("login");
			}
		});
	}
});

Template.login.events({
	"click #googleLogin": function(e) {
		Meteor.loginWithGoogle({"loginUrlParameters": {"hd": Meteor.settings.public.restrictedDomain}}, function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("target");
			}
		});
	}
});

Template.dashboard.helpers({
	"background": function() {
		if(Meteor.user() && Meteor.user().inGame && !Meteor.user().alive) {
			return {"class": "background dead"};
		}
		return {"class": "background"};
	}
});

Template.adminPanel.helpers({
	"actions": function() {
		return Actions.find({}, {sort: {"timestamp": -1}}).fetch();
	},
	"message": function() {
		var a = "Unknown";
		var t = "Unknown";
		var c = "";
		var assassin = Meteor.users.findOne(this.assassin);
		var target = Meteor.users.findOne(this.target);

		if(assassin) {a = assassin.profile.name;}
		if(target) {t = target.profile.name;}

		if(this.type === "quit") {
			return "" + a + " quit the game";
		}
		if(this.type === "status") {
			return this.message;
		}
		if(!this.confirmed) {
			c = '
Waiting for previous target to press "I Was Killed" before updating kill count';
			var prevTarget = Meteor.users.findOne(assassin.target);
			if(prevTarget) {
				c = '
Waiting for ' + prevTarget.profile.name + ' to press "I Was Killed" before updating kill count';
			}
		}

		return "" + this.icon + " " + a + " assassinated " + t + "" + c;
	},
	"userList": function() {
		return Meteor.users.find({}, {sort: {"alive": -1, "profile.name": 1}}).fetch();
	},
	"aliveCount": function() {
		return Meteor.users.find({"inGame": true, "alive": true}).count();
	},
	"killCount": function() {
		return Actions.find({"type": "kill", "confirmed": true}).count();
	},
	"kill24Count": function() {
		var past24 = Date.now() - 24 * 60 * 60 * 1000;
		return Actions.find({"timestamp": {$gt: past24}, "type": "kill", "confirmed": true}).count();
	},
	"playerCount": function() {
		return Meteor.users.find().count();
	},
	"statusStyle": function() {
		if(this.alive) {
			return "color: #333;";
		}
		return "color: red; text-decoration: line-through;";
	},
	"targetName": function() {
		var target = Meteor.users.findOne(this.target);
		if(target) {
			return Meteor.users.findOne(this.target).profile.name;
		}
		return "Unknown";
	},
	settings: function() {
		return {
			position: "bottom",
			limit: 8,
			rules: [
				{
					collection: Meteor.users,
					filter: {"_id": {$ne: Session.get("configureAssassin")}},
					field: "profile.name",
					template: Template.userOption
				}
			]
		};
	}
});

Template.adminPanel.events({
	"click #startGame": function() {
		swal({
			title: "Are you sure you want to start a new game?",
			text: "This will reset all scores, empty the action log and assign new targets. Note: This is a destructive action.",
			type: "warning",
			showCancelButton: true,
			confirmButtonText: "Yes",
			confirmButtonColor: "#d9534f",
			cancelButtonText: "No",
		},
		function(confirmed) {
			if(confirmed) {
				Meteor.call("startGame");
			}
		});
	},
	"shown.bs.modal #broadcastModal": function(e) {
		$(e.target).find("#message").focus();
	},
	"click #broadcast": function(e) {
		var input = $(e.target).closest(".modal").find("#message");
		var message = input.val();
		input.val("");
		Meteor.call("broadcast", message, function(error) {
			if(error) {
				alert(error);
			}
		});
	},
	"show.bs.modal #controlPanel": function(e) {
		var a = $(e.relatedTarget).data("assassin");
		Session.set("configureAssassin", a);
		var name = Meteor.users.findOne(a).profile.name;
		$(e.target).find("#name").val(name);
		$(e.target).find(".assassinName").text(name);
	},
	"click #changeDisplayName": function(e) {
		var input = $(e.target).closest(".modal").find("#name");
		var name = input.val();
		input.val("");
		Meteor.call("changeDisplayName", Session.get("configureAssassin"), name, function(error) {
			if(error) {
				alert(error);
			}
		});
	},
	"click #reassignTarget": function(e) {
		var input = $(e.target).closest(".modal").find("#target");
		var name = input.val();
		input.val("");
		var newTarget = Meteor.users.findOne({"profile.name": name});
		if(newTarget) {
			Meteor.call("reassignTarget", Session.get("configureAssassin"), newTarget._id, function(error) {
				if(error) {
					alert(error);
				}
			});
		}
		else {
			alert("Please pick a name from the list.");
		}
	},
	"click #forceKill": function(e) {
		var input = $(e.target).closest(".modal").find("#assassin");
		var name = input.val();
		input.val("");
		var assassin = Meteor.users.findOne({"profile.name": name});
		if(assassin) {
			Meteor.call("killed", Session.get("configureAssassin"), assassin._id, function(error) {
				if(error) {
					alert(error);
				}
			});
		}
		else {
			alert("Please pick a name from the list.");
		}
	},
	"click #forceQuit": function(e) {
		Meteor.call("quit", Session.get("configureAssassin"), function(error) {
			if(error) {
				alert(error);
			}
		});
	}
});

Template.userOption.helpers({
	"style": function() {
		if(this.alive) {
			return {};
		}
		return {style: "color: red; text-decoration: line-through;"};
	}
});

Template.target.helpers({
	"alive": function() {
		if(Meteor.user()) {
			return Meteor.user().alive;
		}
		return false;
	},
	"target": function() {
		return Meteor.users.findOne(Meteor.user().target).profile.name;
	},
	"relevantActions": function() {
		return Actions.find({
			$or: [
				{"type": "status"},
				{"assassin": Meteor.userId()},
				{"target": Meteor.userId()}
			]
		}, {sort: {"timestamp": -1}}).fetch();
	},
	"allActions": function() {
		return Actions.find({}, {sort: {"timestamp": -1}}).fetch();
	},
	"message": function() {
		var a = "Unknown";
		var t = "Unknown";
		var c = "";
		var assassin = Meteor.users.findOne(this.assassin);
		var target = Meteor.users.findOne(this.target);

		if(this.assassin === Meteor.userId()) {a = "You";}
		else if(assassin) {a = assassin.profile.name;}
		if(this.target === Meteor.userId()) {t = "You";}
		else if(target) {t = target.profile.name;}

		if(this.type === "quit") {
			return "" + a + " quit the game";
		}
		if(this.type === "status") {
			return this.message;
		}
		if(!this.confirmed && this.assassin === Meteor.userId()) {
			c = '
Waiting for previous target to press "I Was Killed" before updating kill count';
			var prevTarget = Meteor.users.findOne(assassin.target);
			if(prevTarget) {
				c = '
Waiting for ' + prevTarget.profile.name + ' to press "I Was Killed" before updating kill count';
			}
		}

		return "" + this.icon + " " + a + " assassinated " + t + "" + c;
	},
	settings: function() {
		return {
			position: "bottom",
			limit: 8,
			rules: [
				{
					collection: Meteor.users,
					filter: {"_id": {$ne: Meteor.userId()}},
					field: "profile.name",
					template: Template.userOption
				}
			]
		};
	}
});

Template.target.events({
	"shown.bs.modal #killModal": function(e) {
		$(e.target).find("#assassin").focus();
	},
	"click #killed": function(e) {
		var input = $(e.target).closest(".modal").find("#assassin");
		var assassinName = input.val();
		input.val("");
		var assassin = Meteor.users.findOne({"profile.name": assassinName});
		if(assassin) {
			Meteor.call("killed", Meteor.userId(), assassin._id, function(error) {
				if(error) {
					alert(error);
				}
			});
		}
		else {
			alert("Please pick a name from the list.");
		}
	},
	"click #quit": function(e) {
		Meteor.call("quit", Meteor.userId(), function(error) {
			if(error) {
				alert(error);
			}
		});
	}
});

Template.leaderboard.helpers({
	"playerCount": function() {
		return Meteor.users.find({"inGame": true, "alive": true}).count();
	},
	"userList": function() {
		return Meteor.users.find({"inGame": true}, {sort: {"kills": -1, "alive": -1, "profile.name": 1}}).fetch();
	}
});<commit_msg>Make playerCount only contain players who are inGame<commit_after>Template.registerHelper("activeRoute", function(route) {
	var currentRoute = Router.current().route.getName();
	return currentRoute === route ? " active" : "";
});

Template.registerHelper("calendarTime", function(date) {
	if(date) {
		return moment(date).calendar();
	}
	return "Unknown";
});

Template.registerHelper("equals", function(a, b) {
	return a === b;
});

Template.navigation.events({
	"click #logout": function(e) {
		Meteor.logout(function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("login");
			}
		});
	}
});

Template.login.events({
	"click #googleLogin": function(e) {
		Meteor.loginWithGoogle({"loginUrlParameters": {"hd": Meteor.settings.public.restrictedDomain}}, function(error) {
			if(error) {
				alert(error);
			}
			else {
				Router.go("target");
			}
		});
	}
});

Template.dashboard.helpers({
	"background": function() {
		if(Meteor.user() && Meteor.user().inGame && !Meteor.user().alive) {
			return {"class": "background dead"};
		}
		return {"class": "background"};
	}
});

Template.adminPanel.helpers({
	"actions": function() {
		return Actions.find({}, {sort: {"timestamp": -1}}).fetch();
	},
	"message": function() {
		var a = "Unknown";
		var t = "Unknown";
		var c = "";
		var assassin = Meteor.users.findOne(this.assassin);
		var target = Meteor.users.findOne(this.target);

		if(assassin) {a = assassin.profile.name;}
		if(target) {t = target.profile.name;}

		if(this.type === "quit") {
			return "" + a + " quit the game";
		}
		if(this.type === "status") {
			return this.message;
		}
		if(!this.confirmed) {
			c = '
Waiting for previous target to press "I Was Killed" before updating kill count';
			var prevTarget = Meteor.users.findOne(assassin.target);
			if(prevTarget) {
				c = '
Waiting for ' + prevTarget.profile.name + ' to press "I Was Killed" before updating kill count';
			}
		}

		return "" + this.icon + " " + a + " assassinated " + t + "" + c;
	},
	"userList": function() {
		return Meteor.users.find({}, {sort: {"alive": -1, "profile.name": 1}}).fetch();
	},
	"aliveCount": function() {
		return Meteor.users.find({"inGame": true, "alive": true}).count();
	},
	"killCount": function() {
		return Actions.find({"type": "kill", "confirmed": true}).count();
	},
	"kill24Count": function() {
		var past24 = Date.now() - 24 * 60 * 60 * 1000;
		return Actions.find({"timestamp": {$gt: past24}, "type": "kill", "confirmed": true}).count();
	},
	"playerCount": function() {
		return Meteor.users.find({"inGame": true}).count();
	},
	"statusStyle": function() {
		if(this.alive) {
			return "color: #333;";
		}
		return "color: red; text-decoration: line-through;";
	},
	"targetName": function() {
		var target = Meteor.users.findOne(this.target);
		if(target) {
			return Meteor.users.findOne(this.target).profile.name;
		}
		return "Unknown";
	},
	settings: function() {
		return {
			position: "bottom",
			limit: 8,
			rules: [
				{
					collection: Meteor.users,
					filter: {"_id": {$ne: Session.get("configureAssassin")}},
					field: "profile.name",
					template: Template.userOption
				}
			]
		};
	}
});

Template.adminPanel.events({
	"click #startGame": function() {
		swal({
			title: "Are you sure you want to start a new game?",
			text: "This will reset all scores, empty the action log and assign new targets. Note: This is a destructive action.",
			type: "warning",
			showCancelButton: true,
			confirmButtonText: "Yes",
			confirmButtonColor: "#d9534f",
			cancelButtonText: "No",
		},
		function(confirmed) {
			if(confirmed) {
				Meteor.call("startGame");
			}
		});
	},
	"shown.bs.modal #broadcastModal": function(e) {
		$(e.target).find("#message").focus();
	},
	"click #broadcast": function(e) {
		var input = $(e.target).closest(".modal").find("#message");
		var message = input.val();
		input.val("");
		Meteor.call("broadcast", message, function(error) {
			if(error) {
				alert(error);
			}
		});
	},
	"show.bs.modal #controlPanel": function(e) {
		var a = $(e.relatedTarget).data("assassin");
		Session.set("configureAssassin", a);
		var name = Meteor.users.findOne(a).profile.name;
		$(e.target).find("#name").val(name);
		$(e.target).find(".assassinName").text(name);
	},
	"click #changeDisplayName": function(e) {
		var input = $(e.target).closest(".modal").find("#name");
		var name = input.val();
		input.val("");
		Meteor.call("changeDisplayName", Session.get("configureAssassin"), name, function(error) {
			if(error) {
				alert(error);
			}
		});
	},
	"click #reassignTarget": function(e) {
		var input = $(e.target).closest(".modal").find("#target");
		var name = input.val();
		input.val("");
		var newTarget = Meteor.users.findOne({"profile.name": name});
		if(newTarget) {
			Meteor.call("reassignTarget", Session.get("configureAssassin"), newTarget._id, function(error) {
				if(error) {
					alert(error);
				}
			});
		}
		else {
			alert("Please pick a name from the list.");
		}
	},
	"click #forceKill": function(e) {
		var input = $(e.target).closest(".modal").find("#assassin");
		var name = input.val();
		input.val("");
		var assassin = Meteor.users.findOne({"profile.name": name});
		if(assassin) {
			Meteor.call("killed", Session.get("configureAssassin"), assassin._id, function(error) {
				if(error) {
					alert(error);
				}
			});
		}
		else {
			alert("Please pick a name from the list.");
		}
	},
	"click #forceQuit": function(e) {
		Meteor.call("quit", Session.get("configureAssassin"), function(error) {
			if(error) {
				alert(error);
			}
		});
	}
});

Template.userOption.helpers({
	"style": function() {
		if(this.alive) {
			return {};
		}
		return {style: "color: red; text-decoration: line-through;"};
	}
});

Template.target.helpers({
	"alive": function() {
		if(Meteor.user()) {
			return Meteor.user().alive;
		}
		return false;
	},
	"target": function() {
		return Meteor.users.findOne(Meteor.user().target).profile.name;
	},
	"relevantActions": function() {
		return Actions.find({
			$or: [
				{"type": "status"},
				{"assassin": Meteor.userId()},
				{"target": Meteor.userId()}
			]
		}, {sort: {"timestamp": -1}}).fetch();
	},
	"allActions": function() {
		return Actions.find({}, {sort: {"timestamp": -1}}).fetch();
	},
	"message": function() {
		var a = "Unknown";
		var t = "Unknown";
		var c = "";
		var assassin = Meteor.users.findOne(this.assassin);
		var target = Meteor.users.findOne(this.target);

		if(this.assassin === Meteor.userId()) {a = "You";}
		else if(assassin) {a = assassin.profile.name;}
		if(this.target === Meteor.userId()) {t = "You";}
		else if(target) {t = target.profile.name;}

		if(this.type === "quit") {
			return "" + a + " quit the game";
		}
		if(this.type === "status") {
			return this.message;
		}
		if(!this.confirmed && this.assassin === Meteor.userId()) {
			c = '
Waiting for previous target to press "I Was Killed" before updating kill count';
			var prevTarget = Meteor.users.findOne(assassin.target);
			if(prevTarget) {
				c = '
Waiting for ' + prevTarget.profile.name + ' to press "I Was Killed" before updating kill count';
			}
		}

		return "" + this.icon + " " + a + " assassinated " + t + "" + c;
	},
	settings: function() {
		return {
			position: "bottom",
			limit: 8,
			rules: [
				{
					collection: Meteor.users,
					filter: {"_id": {$ne: Meteor.userId()}},
					field: "profile.name",
					template: Template.userOption
				}
			]
		};
	}
});

Template.target.events({
	"shown.bs.modal #killModal": function(e) {
		$(e.target).find("#assassin").focus();
	},
	"click #killed": function(e) {
		var input = $(e.target).closest(".modal").find("#assassin");
		var assassinName = input.val();
		input.val("");
		var assassin = Meteor.users.findOne({"profile.name": assassinName});
		if(assassin) {
			Meteor.call("killed", Meteor.userId(), assassin._id, function(error) {
				if(error) {
					alert(error);
				}
			});
		}
		else {
			alert("Please pick a name from the list.");
		}
	},
	"click #quit": function(e) {
		Meteor.call("quit", Meteor.userId(), function(error) {
			if(error) {
				alert(error);
			}
		});
	}
});

Template.leaderboard.helpers({
	"playerCount": function() {
		return Meteor.users.find({"inGame": true, "alive": true}).count();
	},
	"userList": function() {
		return Meteor.users.find({"inGame": true}, {sort: {"kills": -1, "alive": -1, "profile.name": 1}}).fetch();
	}
});<|endoftext|>


<commit_before>
const Viewport = require('./viewport');
const RG = require('../src/battles');

// TODO: Refactor out of this file
/* Builds and returns two arrays. First contains all CSS classNames of
 * cells to be rendered, and the second one all characters to be rendered.*/
const getClassesAndChars = function(seen, cells, selCell) {
    const cssClasses = [];
    const asciiChars = [];

    let selX = -1;
    let selY = -1;

    if (selCell !== null) {
        selX = selCell.getX();
        selY = selCell.getY();
    }

    // TODO: Prevents a bug, if player wants to see inventory right after
    // Load. Should render the visible cells properly though.
    if (!seen) {
        cssClasses.fill('cell-not-seen', 0, cells.length - 1);
        asciiChars.fill('X', 0, cells.length - 1);
        return [cssClasses, asciiChars];
    }

    for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];
        const cellIndex = seen.indexOf(cell);
        const visibleToPlayer = cellIndex < 0 ? false : true;

        let cellClass = RG.getClassName(cell, visibleToPlayer);
        const cellChar = RG.getChar(cell, visibleToPlayer);

        if (selX === cell.getX() && selY === cell.getY()) {
            cellClass = 'cell-target-selected';
        }

        if (!visibleToPlayer) {
            if (cell.isExplored()) {cellClass += ' cell-not-seen';}
        }
        cssClasses.push(cellClass);
        asciiChars.push(cellChar);
    }

    return [cssClasses, asciiChars];
};

const getClassesAndCharsWithRLE = function(seen, cells, selCell, anim) {
    let prevChar = null;
    let prevClass = null;
    let charRL = 0;
    let classRL = 0;

    const cssClasses = [];
    const asciiChars = [];

    let selX = -1;
    let selY = -1;

    if (selCell) {
        selX = selCell.getX();
        selY = selCell.getY();
    }

    // TODO: Prevents a bug, if player wants to see inventory right after
    // Load. Should render the visible cells properly though.
    if (!seen) {
        cssClasses.fill('cell-not-seen', 0, cells.length - 1);
        asciiChars.fill('X', 0, cells.length - 1);
        return [cssClasses, asciiChars];
    }

    let cellClass = '';
    let cellChar = '';

    for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];
        const cellX = cell.getX();
        const cellY = cell.getY();

        const cellIndex = seen.indexOf(cell);
        const visibleToPlayer = cellIndex < 0 ? false : true;

        cellClass = RG.getClassName(cell, visibleToPlayer);
        cellChar = RG.getChar(cell, visibleToPlayer);

        // Useless to animate non-visible cells
        if (visibleToPlayer && anim) {
            const key = cellX + ',' + cellY;
            if (anim[key]) {
                cellClass = anim[key].className;
                cellChar = anim[key].char;
            }
        }

        if (cell.isAtXY(selX, selY)) {
            cellClass = 'cell-target-selected';
        }

        if (!visibleToPlayer) {
            if (cell.isExplored()) {cellClass += ' cell-not-seen';}
        }

        const finishRLE = (cellClass !== prevClass) && prevClass
            || (cellChar !== prevChar) && prevChar;

        if (finishRLE) {
            cssClasses.push([classRL, prevClass]);
            classRL = 1;
            asciiChars.push([charRL, prevChar]);
            charRL = 1;
        }
        else {
            ++classRL;
            ++charRL;
        }

        prevChar = cellChar;
        prevClass = cellClass;
    }

    // Need to add the remaining cells
    if (classRL > 0) {cssClasses.push([classRL, cellClass]);}
    if (charRL > 0) {asciiChars.push([charRL, cellChar]);}

    return [cssClasses, asciiChars];
};

/* Same as above but optimized for showing the full map in the game editor.
*  Does not take into account cells seen by player. */
const getClassesAndCharsFullMap = function(cells, selCell) {
    const cssClasses = [];
    const asciiChars = [];

    let selX = -1;
    let selY = -1;

    if (selCell !== null) {
        selX = selCell.getX();
        selY = selCell.getY();
    }

    for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];

        let cellClass = RG.getClassNameFullMap(cell);
        const cellChar = RG.getCharFullMap(cell);

        if (selX === cell.getX() && selY === cell.getY()) {
            cellClass = 'cell-target-selected';
        }

        cssClasses.push(cellClass);
        asciiChars.push(cellChar);
    }

    return [cssClasses, asciiChars];
};

/* Returns the CSS classes + characters to be rendered using RLE. */
const getClassesAndCharsFullMapWithRLE = function(cells, selCell) {
    let prevChar = null;
    let prevClass = null;
    let charRL = 0;
    let classRL = 0;

    const cssClasses = [];
    const asciiChars = [];

    let selX = -1;
    let selY = -1;

    if (selCell !== null) {
        selX = selCell.getX();
        selY = selCell.getY();
    }

    let cellClass = '';
    let cellChar = '';

    for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];

        cellClass = RG.getClassNameFullMap(cell);
        cellChar = RG.getCharFullMap(cell);

        if (selX === cell.getX() && selY === cell.getY()) {
            cellClass = 'cell-target-selected';
        }

        const finishRLE = (cellClass !== prevClass) && prevClass
            || (cellChar !== prevChar) && prevChar;

        if (finishRLE) {
            cssClasses.push([classRL, prevClass]);
            classRL = 1;
            asciiChars.push([charRL, prevChar]);
            charRL = 1;
        }
        else {
            ++classRL;
            ++charRL;
        }

        prevChar = cellChar;
        prevClass = cellClass;
    }

    // Need to add the remaining cells
    if (classRL > 0) {cssClasses.push([classRL, cellClass]);}
    if (charRL > 0) {asciiChars.push([charRL, cellChar]);}

    return [cssClasses, asciiChars];

};

/* Creates a screen with viewport set to given parameters. */
const Screen = function(viewX, viewY) {
    this.viewportX = viewX;
    this.viewportY = viewY;
    this.selectedCell = null;

    let _charRows = [];
    let _classRows = [];
    let _mapShown = false;

    this.viewport = new Viewport(viewX, viewY);

    /* Returns the leftmost X-coordinate of the viewport. */
    this.getStartX = function() {
        return this.viewport.startX;
    };

    this.setSelectedCell = function(cell) {
        this.selectedCell = cell;
    };

    this.setViewportXY = function(x, y) {
        this.viewportX = x;
        this.viewportY = y;
        this.viewport.setViewportXY(x, y);

        _charRows = [];
        _classRows = [];
        for (let yy = 0; yy < y; yy++) {
            _charRows.push([]);
            _classRows.push([]);
        }
    };

    this.setMapShown = function(mapShown) {
        _mapShown = mapShown;
    };

    this.getCharRows = function() {
        return _charRows;
    };

    this.getClassRows = function() {
        return _classRows;
    };

    this._initRender = function(playX, playY, map) {
        if (!_mapShown) {
            this.setViewportXY(this.viewportX,
                this.viewportY);
        }
        else {
            this.setViewportXY(map.cols, map.rows);
        }
        this.viewport.getCellsInViewPort(playX, playY, map);

        this.startX = this.viewport.startX;
        this.endX = this.viewport.endX;
        this.startY = this.viewport.startY;
        this.endY = this.viewport.endY;
    };

    /* 'Renders' the ASCII screen and style classes based on player's
     * coordinate, map and visible cells. */
    this.render = function(playX, playY, map, visibleCells) {
        this._initRender(playX, playY, map);
        let yCount = 0;
        for (let y = this.viewport.startY; y <= this.viewport.endY; ++y) {
            const rowCellData = this.viewport.getCellRow(y);
            const classesChars = getClassesAndChars(visibleCells,
                rowCellData, this.selectedCell);

            _classRows[yCount] = classesChars[0];
            _charRows[yCount] = classesChars[1];
            ++yCount;
        }

    };

    this.renderWithRLE = function(playX, playY, map, visibleCells, anim) {
        this._initRender(playX, playY, map);
        let yCount = 0;
        for (let y = this.viewport.startY; y <= this.viewport.endY; ++y) {
            const rowCellData = this.viewport.getCellRow(y);
            const classesChars = getClassesAndCharsWithRLE(visibleCells,
                rowCellData, this.selectedCell, anim);

            _classRows[yCount] = classesChars[0];
            _charRows[yCount] = classesChars[1];
            ++yCount;
        }
    };

    /* Renders the full map as visible. */
    this.renderFullMap = function(map) {
        this.startX = 0;
        this.endX = map.cols - 1;
        this.startY = 0;
        this.endY = map.rows - 1;

        for (let y = 0; y < map.rows; ++y) {
            const classesChars = getClassesAndCharsFullMap(
                map.getCellRowFast(y), this.selectedCell);

            _classRows[y] = classesChars[0];
            _charRows[y] = classesChars[1];
        }
    };

    this.renderFullMapWithRLE = function(map) {
        this.startX = 0;
        this.endX = map.cols - 1;
        this.startY = 0;
        this.endY = map.rows - 1;

        for (let y = 0; y < map.rows; ++y) {
            const classesChars = getClassesAndCharsFullMapWithRLE(
                map.getCellRowFast(y), this.selectedCell);

            _classRows[y] = classesChars[0];
            _charRows[y] = classesChars[1];
        }
    };

    this.clear = function() {
        _classRows = [];
        _charRows = [];
        this.selectedCell = null;
        this.startX = 0;
        this.endX = -1;
        this.startY = 0;
        this.endY = -1;
    };

};

module.exports = Screen;
<commit_msg>Full map rendering (used in editor) supports highlighting multiple cells now.<commit_after>
const Viewport = require('./viewport');
const RG = require('../src/battles');

// TODO: Refactor out of this file
/* Builds and returns two arrays. First contains all CSS classNames of
 * cells to be rendered, and the second one all characters to be rendered.*/
const getClassesAndChars = function(seen, cells, selCell) {
    const cssClasses = [];
    const asciiChars = [];

    let selX = -1;
    let selY = -1;

    if (selCell !== null) {
        selX = selCell.getX();
        selY = selCell.getY();
    }

    // TODO: Prevents a bug, if player wants to see inventory right after
    // Load. Should render the visible cells properly though.
    if (!seen) {
        cssClasses.fill('cell-not-seen', 0, cells.length - 1);
        asciiChars.fill('X', 0, cells.length - 1);
        return [cssClasses, asciiChars];
    }

    for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];
        const cellIndex = seen.indexOf(cell);
        const visibleToPlayer = cellIndex < 0 ? false : true;

        let cellClass = RG.getClassName(cell, visibleToPlayer);
        const cellChar = RG.getChar(cell, visibleToPlayer);

        if (selX === cell.getX() && selY === cell.getY()) {
            cellClass = 'cell-target-selected';
        }

        if (!visibleToPlayer) {
            if (cell.isExplored()) {cellClass += ' cell-not-seen';}
        }
        cssClasses.push(cellClass);
        asciiChars.push(cellChar);
    }

    return [cssClasses, asciiChars];
};

const getClassesAndCharsWithRLE = function(seen, cells, selCell, anim) {
    let prevChar = null;
    let prevClass = null;
    let charRL = 0;
    let classRL = 0;

    const cssClasses = [];
    const asciiChars = [];

    let selX = -1;
    let selY = -1;

    if (selCell) {
        selX = selCell.getX();
        selY = selCell.getY();
    }

    // TODO: Prevents a bug, if player wants to see inventory right after
    // Load. Should render the visible cells properly though.
    if (!seen) {
        cssClasses.fill('cell-not-seen', 0, cells.length - 1);
        asciiChars.fill('X', 0, cells.length - 1);
        return [cssClasses, asciiChars];
    }

    let cellClass = '';
    let cellChar = '';

    for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];
        const cellX = cell.getX();
        const cellY = cell.getY();

        const cellIndex = seen.indexOf(cell);
        const visibleToPlayer = cellIndex < 0 ? false : true;

        cellClass = RG.getClassName(cell, visibleToPlayer);
        cellChar = RG.getChar(cell, visibleToPlayer);

        // Useless to animate non-visible cells
        if (visibleToPlayer && anim) {
            const key = cellX + ',' + cellY;
            if (anim[key]) {
                cellClass = anim[key].className;
                cellChar = anim[key].char;
            }
        }

        if (cell.isAtXY(selX, selY)) {
            cellClass = 'cell-target-selected';
        }

        if (!visibleToPlayer) {
            if (cell.isExplored()) {cellClass += ' cell-not-seen';}
        }

        const finishRLE = (cellClass !== prevClass) && prevClass
            || (cellChar !== prevChar) && prevChar;

        if (finishRLE) {
            cssClasses.push([classRL, prevClass]);
            classRL = 1;
            asciiChars.push([charRL, prevChar]);
            charRL = 1;
        }
        else {
            ++classRL;
            ++charRL;
        }

        prevChar = cellChar;
        prevClass = cellClass;
    }

    // Need to add the remaining cells
    if (classRL > 0) {cssClasses.push([classRL, cellClass]);}
    if (charRL > 0) {asciiChars.push([charRL, cellChar]);}

    return [cssClasses, asciiChars];
};

/* Same as above but optimized for showing the full map in the game editor.
*  Does not take into account cells seen by player. */
const getClassesAndCharsFullMap = function(cells, selCell) {
    const cssClasses = [];
    const asciiChars = [];

    let selX = -1;
    let selY = -1;

    if (selCell !== null) {
        selX = selCell.getX();
        selY = selCell.getY();
    }

    for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];

        let cellClass = RG.getClassNameFullMap(cell);
        const cellChar = RG.getCharFullMap(cell);

        if (selX === cell.getX() && selY === cell.getY()) {
            cellClass = 'cell-target-selected';
        }

        cssClasses.push(cellClass);
        asciiChars.push(cellChar);
    }

    return [cssClasses, asciiChars];
};

/* Returns the CSS classes + characters to be rendered using RLE. */
const getClassesAndCharsFullMapWithRLE = function(cells, selCell) {
    let prevChar = null;
    let prevClass = null;
    let charRL = 0;
    let classRL = 0;

    const cssClasses = [];
    const asciiChars = [];

    let selMap = null;
    if (selCell) {
        selMap = new Map();
        selCell.forEach(cell => {
            selMap.set(cell.getX() + ',' + cell.getY(), cell);
        });
    }

    let cellClass = '';
    let cellChar = '';

    for (let i = 0; i < cells.length; i++) {
        const cell = cells[i];

        cellClass = RG.getClassNameFullMap(cell);
        cellChar = RG.getCharFullMap(cell);

        if (selMap) {
            const [x, y] = [cell.getX(), cell.getY()];
            if (selMap.has(x + ',' + y)) {
                cellClass = 'cell-target-selected';
            }
        }

        const finishRLE = (cellClass !== prevClass) && prevClass
            || (cellChar !== prevChar) && prevChar;

        if (finishRLE) {
            cssClasses.push([classRL, prevClass]);
            classRL = 1;
            asciiChars.push([charRL, prevChar]);
            charRL = 1;
        }
        else {
            ++classRL;
            ++charRL;
        }

        prevChar = cellChar;
        prevClass = cellClass;
    }

    // Need to add the remaining cells
    if (classRL > 0) {cssClasses.push([classRL, cellClass]);}
    if (charRL > 0) {asciiChars.push([charRL, cellChar]);}

    return [cssClasses, asciiChars];

};

/* Creates a screen with viewport set to given parameters. */
const Screen = function(viewX, viewY) {
    this.viewportX = viewX;
    this.viewportY = viewY;
    this.selectedCell = null;

    let _charRows = [];
    let _classRows = [];
    let _mapShown = false;

    this.viewport = new Viewport(viewX, viewY);

    /* Returns the leftmost X-coordinate of the viewport. */
    this.getStartX = function() {
        return this.viewport.startX;
    };

    this.setSelectedCell = function(cell) {
        this.selectedCell = cell;
    };

    this.setViewportXY = function(x, y) {
        this.viewportX = x;
        this.viewportY = y;
        this.viewport.setViewportXY(x, y);

        _charRows = [];
        _classRows = [];
        for (let yy = 0; yy < y; yy++) {
            _charRows.push([]);
            _classRows.push([]);
        }
    };

    this.setMapShown = function(mapShown) {
        _mapShown = mapShown;
    };

    this.getCharRows = function() {
        return _charRows;
    };

    this.getClassRows = function() {
        return _classRows;
    };

    this._initRender = function(playX, playY, map) {
        if (!_mapShown) {
            this.setViewportXY(this.viewportX,
                this.viewportY);
        }
        else {
            this.setViewportXY(map.cols, map.rows);
        }
        this.viewport.getCellsInViewPort(playX, playY, map);

        this.startX = this.viewport.startX;
        this.endX = this.viewport.endX;
        this.startY = this.viewport.startY;
        this.endY = this.viewport.endY;
    };

    /* 'Renders' the ASCII screen and style classes based on player's
     * coordinate, map and visible cells. */
    this.render = function(playX, playY, map, visibleCells) {
        this._initRender(playX, playY, map);
        let yCount = 0;
        for (let y = this.viewport.startY; y <= this.viewport.endY; ++y) {
            const rowCellData = this.viewport.getCellRow(y);
            const classesChars = getClassesAndChars(visibleCells,
                rowCellData, this.selectedCell);

            _classRows[yCount] = classesChars[0];
            _charRows[yCount] = classesChars[1];
            ++yCount;
        }

    };

    this.renderWithRLE = function(playX, playY, map, visibleCells, anim) {
        this._initRender(playX, playY, map);
        let yCount = 0;
        for (let y = this.viewport.startY; y <= this.viewport.endY; ++y) {
            const rowCellData = this.viewport.getCellRow(y);
            const classesChars = getClassesAndCharsWithRLE(visibleCells,
                rowCellData, this.selectedCell, anim);

            _classRows[yCount] = classesChars[0];
            _charRows[yCount] = classesChars[1];
            ++yCount;
        }
    };

    /* Renders the full map as visible. */
    this.renderFullMap = function(map) {
        this.startX = 0;
        this.endX = map.cols - 1;
        this.startY = 0;
        this.endY = map.rows - 1;

        for (let y = 0; y < map.rows; ++y) {
            const classesChars = getClassesAndCharsFullMap(
                map.getCellRowFast(y), this.selectedCell);

            _classRows[y] = classesChars[0];
            _charRows[y] = classesChars[1];
        }
    };

    this.renderFullMapWithRLE = function(map) {
        this.startX = 0;
        this.endX = map.cols - 1;
        this.startY = 0;
        this.endY = map.rows - 1;

        for (let y = 0; y < map.rows; ++y) {
            const classesChars = getClassesAndCharsFullMapWithRLE(
                map.getCellRowFast(y), this.selectedCell);

            _classRows[y] = classesChars[0];
            _charRows[y] = classesChars[1];
        }
    };

    this.clear = function() {
        _classRows = [];
        _charRows = [];
        this.selectedCell = null;
        this.startX = 0;
        this.endX = -1;
        this.startY = 0;
        this.endY = -1;
    };

};

module.exports = Screen;
<|endoftext|>