<commit_before>var videos = {
  tte:["TTTE01.m4v","TTTE02.m4v","TTTE03.m4v","TTTE04.m4v"],
  wiggles:["wig01.m4v","wig02.m4v","wig03.m4v"],
  itng:[],
  music:[],
  teletubbies:[],
  ygg:[],
  hive:[]
}
var selectedVideos = videos.tte;
var currentVideo = 0;

function playFirstVideo(){
  currentVideo = 0;
  playVideo();
}

function playNextVideo(){
  currentVideo++;
  if(currentVideo > selectedVideos.length-1){
    currentVideo = 0;
  }
  playVideo();
}

function playVideo(){
  var video = document.getElementById("Video1");
  if(selectedVideos[currentVideo]){
    video.src = selectedVideos[currentVideo];
    video.load();
    video.play();
  }else{
    video.src = "";
  }
  video.webkitRequestFullscreen();
}

function restartVideo() {
  var video = document.getElementById("Video1");
  video.currentTime = 0;
}

function captureKeyStroke(){
  switch(window.event.keyCode){
    case 32:                //space
      playNextVideo();
      break;
    case 119:               //w
      selectedVideos = videos.wiggles;
      playFirstVideo();
      break;
    case 101:               //e
      selectedVideos = videos.tte;
      playFirstVideo();
      break;
    case 105:               //i
      selectedVideos = videos.itng;
      playFirstVideo();
      break;
    case 109:               //m
      selectedVideos = videos.music;
      playFirstVideo();
      break;
    case 116:               //t
      selectedVideos = videos.teletubbies;
      playFirstVideo();
      break;
    case 121:               //y
      selectedVideos = videos.ygg;
      playFirstVideo();
      break;
    case 98:               //b
      selectedVideos = videos.hive;
      playFirstVideo();
      break;
  }
}

<commit_msg>Looping Videos and Remembering Location<commit_after>var videos = {
  ttte:["TTTE01.m4v","TTTE02.m4v","TTTE03.m4v","TTTE04.m4v"],
  wiggles:["WIGGLES01.mp4","WIGGLES02.mp4","WIGGLES03.mp4","WIGGLES04.mp4","WIGGLES05.mp4","WIGGLES06.mp4","WIGGLES07.mp4","WIGGLES08.mp4","WIGGLES09.mp4","WIGGLES10.mp4","WIGGLES11.mp4"],
  wig:["wig01.m4v","wig02.m4v","wig03.m4v"],
  music:[],
  bear:["BEAR01.mp4","BEAR02.mp4","BEAR03.mp4","BEAR04.mp4","BEAR05.mp4","BEAR06.mp4","BEAR07.mp4","BEAR08.mp4","BEAR09.mp4","BEAR10.mp4","BEAR11.mp4","BEAR12.mp4","BEAR13.mp4","BEAR14.mp4","BEAR15.mp4","BEAR16.mp4"],
  gabba:["GABBA01.mp4","GABBA02.mp4","GABBA03.mp4","GABBA04.mp4","GABBA05.mp4","GABBA06.mp4","GABBA07.mp4","GABBA08.mp4","GABBA09.mp4","GABBA10.mp4","GABBA11.mp4","GABBA12.mp4","GABBA13.mp4","GABBA14.mp4","GABBA15.mp4","GABBA16.mp4","GABBA17.mp4","GABBA18.mp4","GABBA19.mp4","GABBA20.mp4"],
  hive:["HIVE01.mp4","HIVE02.mp4"]
}
var selectedVideos = videos.hive;
state = "start";
var currentVideo = 0;
var gabbaIndex = -1;
var wigglesIndex = -1;
var bearIndex = -1;
var ttteIndex = -1;
var wigIndex = -1;

function playVideo(){
  var video = document.getElementById("Video1");
  if(selectedVideos[currentVideo]){
    video.src = selectedVideos[currentVideo];
    video.load();
    video.play();
  }else{
    video.src = "";
  }
  video.webkitRequestFullscreen();
}

function vidpause(){
  var video = document.getElementById("Video1");
  video.pause();
}

function restartVideo() {
  var video = document.getElementById("Video1");
  video.currentTime = 0;
}

function captureKeyStroke(){
  switch(window.event.keyCode){
    case 32:                //space
      playNextVideo();
      break;
    case 119:               //w
      selectedVideos = videos.wiggles;
      wigglesIndex++;
      if(wigglesIndex > selectedVideos.length-1){
            wigglesIndex = 0;
      }
      currentVideo = wigglesIndex;
      playVideo();
      break;
    case 115:               //s
      vidpause();
      break;
    case 105:               //i
      selectedVideos = videos.wig;
      wigIndex++;
      if(wigIndex > selectedVideos.length-1){
            wigIndex = 0;
      }
      currentVideo = wigIndex;
      playVideo();
      break;
    case 109:               //m
      selectedVideos = videos.ttte;
      ttteIndex++;
      if(ttteIndex > selectedVideos.length-1){
            ttteIndex = 0;
      }
      currentVideo = ttteIndex;
      playVideo();
      break;
    case 100:               //d
      selectedVideos = videos.bear;
      bearIndex++;
      if(bearIndex > selectedVideos.length-1){
            bearIndex = 0;
      }
      currentVideo = bearIndex;
      playVideo();
      break;
    case 97:               //a
      selectedVideos = videos.gabba;
      gabbaIndex++;
      if(gabbaIndex > selectedVideos.length-1){
            gabbaIndex = 0;
      }
      currentVideo = gabbaIndex;
      playVideo();
      break;
    case 98:               //b

      break;
  }
}

<|endoftext|>


<commit_before>define('doc', ['event'], function(event) {
	var matcher = {
		isTag : function(selector) {
			return selector.match(/^\w+$/);
		},
		isClass : function(selector) {
			return selector.match(/^\.[\w-]+$/);
		},
		isId : function(selector) {
			return selector.match(/^#[\w-]+$/);
		}
	};

	var search = function(namespace, selector) {
		var selector = selector.replace(/^\s+|\s+$/g, '');
		if(matcher.isTag(selector)) {
			return convertToArray(namespace.getElementsByTagName(selector));
		} else if(matcher.isId(selector)) {
			selector = selector.replace('#', '');
			return document.getElementById(selector);
		} else if (matcher.isClass(selector) && namespace.getElementsByClassName) {
			selector = selector.replace('.', '');
			return convertToArray(namespace.getElementsByClassName(selector));
		}
		return convertToArray(namespace.querySelectorAll(selector));
	};

	var convertToArray = function(nodeList) {
		try {
			return Array.prototype.slice.call(namespace.getElementsByTagName(selector));
		} catch(e) {
			var array = [];
			var length = nodeList.length;
			for(var i = 0; i < length; i++) {
				array.push(nodeList[i]);
			}
			return array;
		}
	};

	var fallbackHasClass = function(el, clazz) {
		return el.className.match(new RegExp('(^|\\s)' + clazz + '($|\\s)'));
	};

	var query = function(elements) {
		var selectedElements = [];

		if (elements){
			if (elements instanceof Array) {
				selectedElements = elements;
			} else {
				selectedElements.push(elements);
			}
		}

		return {
			'els' : selectedElements,
			'size' : selectedElements.length,

			'each' : function(command) {
				for(var i = 0; i < this.size; i++) {
					command(this.els[i], i);
				}
			},

			'data' : function(key, value) {
				// Precisa ser assim, pois pode vir string vazia e deve entrar nesse if
				if(value !== undefined) {
					this.each(function(el) {
						el.setAttribute("data-" + key, value);
					});
					return this;
				}
				if(this.first()) {
					return this.first().getAttribute("data-" + key);
				}
				return "";
			},

			'val' : function(newValue) {
				// Precisa ser assim, pois pode vir string vazia e deve entrar nesse if
				if(newValue !== undefined) {
					this.each(function(el) {
						el.value = newValue;
					});
					return this;
				}
				if(this.first()) {
					return this.first().value;
				}
				return "";
			},

			'html' : function(newValue) {
				if(newValue !== undefined) {
					this.each(function(el) {
						el.innerHTML = newValue;
					});
					return this;
				}
				if(this.first()) {
					return this.first().innerHTML;
				}
				return "";
			},

			'append' : function(value) {
				var appendElement;
				this.each(function(el) {
					appendElement = el.appendChild(value);
				});
				return query(appendElement);
			},

			'text' : function(val) {
				var hasInnerText = (document.getElementsByTagName("body")[0].innerText != undefined) ? true : false;

				var currentValue = "";
				this.each(function(el) {
					if (typeof val === 'undefined') {
						currentValue = el.textContent || el.innerText;
					} else {
						if (!hasInnerText) {
							el.textContent = val;
						} else {
							el.innerText = val;
						}
					}
				});
				return currentValue.trim();
			},

			'attr' : function(key, newValue) {
				// Precisa ser assim, pois pode vir string vazia e deve entrar nesse if
				if(newValue !== undefined) {
					this.each(function(el) {
						el.setAttribute(key, newValue);
					});
					return this;
				}
				return this.first().getAttribute(key);
			},

			'hasClass' : function(clazz) {
				var containsClass = false;
				this.each(function(el){
					if(!el.classList) {
						if(fallbackHasClass(el, clazz)) {
							containsClass = true;
						}
					} else if(el.classList.contains(clazz)){
						containsClass = true;
					}
				});
				return containsClass;
			},

			'addClass' : function(clazz) {
				this.each(function(el) {
					if(!el.classList) {
						if(!fallbackHasClass(el, clazz)) {
							el.className += ' ' + clazz + ' ';
						}
					} else {
						el.classList.add(clazz);
					}
				});
				return this;
			},

			'removeClass' : function(clazz) {
				this.each(function(el) {
					if(!el.classList) {
						el.className = el.className.replace(new RegExp('(^|\\s)' + clazz + '($|\\s)'), '');
					} else {
						el.classList.remove(clazz);
					}
				});
				return this;
			},

			'toggleClass' : function(clazz) {
				this.each(function(el) {
					if(!el.classList) {
						var element = query(el);
						if(element.hasClass(clazz)) {
							element.removeClass(clazz);
						} else {
							element.addClass(clazz);
						}
					} else {
						el.classList.toggle(clazz);
					}
				});
				return this;
			},

			'removeItem' : function() {
				this.each(function(el, i){
					if(!el.remove){
						el.parentNode.removeChild(el);
					}else{
						el.remove();
					}
				});
				this.els = [];
				this.size = 0;
				return this;
			},

			'find' : function(selector) {
				var list = [];
				this.each(function(el) {
					var newElement = search(el, selector);
					if(list.indexOf(newElement) === -1) {
						list = list.concat(search(el, selector));
					}
				});
				return query(list);
			},

			'filter': function(filter) {
				var result = [];

				for(var i = 0; i < this.size; i++) {
					if(typeof filter === 'function' && filter(this.els[i])) {
						result.push(this.els[i]);
					} else if(typeof filter === 'string' && this.els[i][filter]) {
						result.push(this.els[i]);
					}
				}
				return query(result);
			},

			'closest' : function(selector) {
				var elements = [];
				var selectorType;

				if (matcher.isTag(selector)) {
					selectorType = 'tag';
				} else if (matcher.isClass(selector)) {
					selector = selector.replace('.', '');
					selectorType = 'class';
				} else if (matcher.isId(selector)) {
					selector = selector.replace('#', '');
					selectorType = 'id';
				}

				if (!selectorType) {
					return null;
				}

				var checkForClosestParent = function(element) {
					if (element) {
						switch (selectorType) {
							case 'tag':
								return (selector.toUpperCase() === element.tagName)
									? element
									: checkForClosestParent(element.parentElement);
								break;
							case 'class':
								return ($(element).hasClass(selector))
									? element
									: checkForClosestParent(element.parentElement);
								break;
							case 'id':
								return (element.id === selector)
									? element
									: checkForClosestParent(element.parentElement);
								break;
							default:
								break;
						}
					}
					return null;
				};

				for (var i = 0; i < this.size; i++) {
					var el = this.els[i].parentElement;

					var closestParent = checkForClosestParent(el);
					if (closestParent) {
						elements.push(closestParent);
					}
				}

				return elements;
			},

			'find' : function(selector) {
				var list = [];
				this.each(function(el) {
					var newElement = search(el, selector);
					if(list.indexOf(newElement) === -1) {
						list = list.concat(search(el, selector));
					}
				});
				return query(list);
			},

			'first' : function() {
				return this.els[0];
			},

			'last' : function() {
				return this.els[this.size - 1];
			},

			'previous' : function() {
				var el = this.els[0];
			    if(el.previousElementSibling) {
			        return query(el.previousElementSibling);
			    } else {
			        while(el = el.previousSibling) {
			            if(el.nodeType === 1) return query(el);
			        }
			    }
			},

			'next' : function() {
				var el = this.els[0];
				if(el.nextElementSibling) {
					return query(el.nextElementSibling);
				} else {
					while(el = el.nextSibling) {
						if(el.nodeType === 1) return query(el);
					}
				}
			},

			'parent' : function() {
				var parents = [];
				for(var i = 0; i < this.size; i++) {
					parents.push(this.els[i].parentElement);
				}
				return query(parents);
			},

			'isEmpty' : function() {
				return this.els === undefined || !this.els.length;
			},

			'on' : function(eventName, command, named) {
				this.each(function(el) {
					event.addEvent(el, eventName, command, named);
				});
				return this;
			},

			'throttle' : function(eventName, command, throttleTime, named) {
				throttleTime = throttleTime || 1000;
				var previous = 0;
				var commandWithThrottle = function(event) {
					var now = + new Date();
					if(previous + throttleTime <= now) {
						command.apply(this, arguments);
						previous = + new Date();
					}
					event.preventDefault();
				}

				return this.on(eventName, commandWithThrottle, named);
			},

			'off' : function(eventName, named) {
				this.each(function(el) {
					event.removeEvent(el, eventName, named);
				});
				return this;
			},

			'selectedText' : function() {
				var input = this.els[0];
				var type = this.attr('type');
				var validSelectionStart = type? type.match('(text|search|password|tel|url)') : false;
				if(document.activeElement !== input) {
					return;
				} else if(validSelectionStart && typeof input.selectionStart !== 'undefined' && input.selectionEnd > 0) {
					return input.value.substring(input.selectionStart, input.selectionEnd);
				} else if (window.getSelection) {
					return window.getSelection().toString();
				} else if (document.getSelection) {
					return document.getSelection().toString();
				} else if (document.selection) {
					if(document.selection.createRange().text) {
						return document.selection.createRange().text;
					}
					return document.selection.createRange().htmlText;
				} else {
					return;
				}
			}
		}
	}

	return function(selector) {
		if(!selector) {
			return selector;
		} else if(typeof selector === "string") {
			return query(search(document, selector));
		}
		return query(selector);
	};
});
<commit_msg>removing duplicates from return<commit_after>define('doc', ['event'], function(event) {
	var matcher = {
		isTag : function(selector) {
			return selector.match(/^\w+$/);
		},
		isClass : function(selector) {
			return selector.match(/^\.[\w-]+$/);
		},
		isId : function(selector) {
			return selector.match(/^#[\w-]+$/);
		}
	};

	var search = function(namespace, selector) {
		var selector = selector.replace(/^\s+|\s+$/g, '');
		if(matcher.isTag(selector)) {
			return convertToArray(namespace.getElementsByTagName(selector));
		} else if(matcher.isId(selector)) {
			selector = selector.replace('#', '');
			return document.getElementById(selector);
		} else if (matcher.isClass(selector) && namespace.getElementsByClassName) {
			selector = selector.replace('.', '');
			return convertToArray(namespace.getElementsByClassName(selector));
		}
		return convertToArray(namespace.querySelectorAll(selector));
	};

	var convertToArray = function(nodeList) {
		try {
			return Array.prototype.slice.call(namespace.getElementsByTagName(selector));
		} catch(e) {
			var array = [];
			var length = nodeList.length;
			for(var i = 0; i < length; i++) {
				array.push(nodeList[i]);
			}
			return array;
		}
	};

	var fallbackHasClass = function(el, clazz) {
		return el.className.match(new RegExp('(^|\\s)' + clazz + '($|\\s)'));
	};

	var query = function(elements) {
		var selectedElements = [];

		if (elements){
			if (elements instanceof Array) {
				selectedElements = elements;
			} else {
				selectedElements.push(elements);
			}
		}

		return {
			'els' : selectedElements,
			'size' : selectedElements.length,

			'each' : function(command) {
				for(var i = 0; i < this.size; i++) {
					command(this.els[i], i);
				}
			},

			'data' : function(key, value) {
				// Precisa ser assim, pois pode vir string vazia e deve entrar nesse if
				if(value !== undefined) {
					this.each(function(el) {
						el.setAttribute("data-" + key, value);
					});
					return this;
				}
				if(this.first()) {
					return this.first().getAttribute("data-" + key);
				}
				return "";
			},

			'val' : function(newValue) {
				// Precisa ser assim, pois pode vir string vazia e deve entrar nesse if
				if(newValue !== undefined) {
					this.each(function(el) {
						el.value = newValue;
					});
					return this;
				}
				if(this.first()) {
					return this.first().value;
				}
				return "";
			},

			'html' : function(newValue) {
				if(newValue !== undefined) {
					this.each(function(el) {
						el.innerHTML = newValue;
					});
					return this;
				}
				if(this.first()) {
					return this.first().innerHTML;
				}
				return "";
			},

			'append' : function(value) {
				var appendElement;
				this.each(function(el) {
					appendElement = el.appendChild(value);
				});
				return query(appendElement);
			},

			'text' : function(val) {
				var hasInnerText = (document.getElementsByTagName("body")[0].innerText != undefined) ? true : false;

				var currentValue = "";
				this.each(function(el) {
					if (typeof val === 'undefined') {
						currentValue = el.textContent || el.innerText;
					} else {
						if (!hasInnerText) {
							el.textContent = val;
						} else {
							el.innerText = val;
						}
					}
				});
				return currentValue.trim();
			},

			'attr' : function(key, newValue) {
				// Precisa ser assim, pois pode vir string vazia e deve entrar nesse if
				if(newValue !== undefined) {
					this.each(function(el) {
						el.setAttribute(key, newValue);
					});
					return this;
				}
				return this.first().getAttribute(key);
			},

			'hasClass' : function(clazz) {
				var containsClass = false;
				this.each(function(el){
					if(!el.classList) {
						if(fallbackHasClass(el, clazz)) {
							containsClass = true;
						}
					} else if(el.classList.contains(clazz)){
						containsClass = true;
					}
				});
				return containsClass;
			},

			'addClass' : function(clazz) {
				this.each(function(el) {
					if(!el.classList) {
						if(!fallbackHasClass(el, clazz)) {
							el.className += ' ' + clazz + ' ';
						}
					} else {
						el.classList.add(clazz);
					}
				});
				return this;
			},

			'removeClass' : function(clazz) {
				this.each(function(el) {
					if(!el.classList) {
						el.className = el.className.replace(new RegExp('(^|\\s)' + clazz + '($|\\s)'), '');
					} else {
						el.classList.remove(clazz);
					}
				});
				return this;
			},

			'toggleClass' : function(clazz) {
				this.each(function(el) {
					if(!el.classList) {
						var element = query(el);
						if(element.hasClass(clazz)) {
							element.removeClass(clazz);
						} else {
							element.addClass(clazz);
						}
					} else {
						el.classList.toggle(clazz);
					}
				});
				return this;
			},

			'removeItem' : function() {
				this.each(function(el, i){
					if(!el.remove){
						el.parentNode.removeChild(el);
					}else{
						el.remove();
					}
				});
				this.els = [];
				this.size = 0;
				return this;
			},

			'find' : function(selector) {
				var list = [];
				this.each(function(el) {
					var newElement = search(el, selector);
					if(list.indexOf(newElement) === -1) {
						list = list.concat(search(el, selector));
					}
				});
				return query(list);
			},

			'filter': function(filter) {
				var result = [];

				for(var i = 0; i < this.size; i++) {
					if(typeof filter === 'function' && filter(this.els[i])) {
						result.push(this.els[i]);
					} else if(typeof filter === 'string' && this.els[i][filter]) {
						result.push(this.els[i]);
					}
				}
				return query(result);
			},

			'closest' : function(selector) {
				var elements = [];

				var selectorType;

				if (matcher.isTag(selector)) {
					selectorType = 'tag';
				} else if (matcher.isClass(selector)) {
					selector = selector.replace('.', '');
					selectorType = 'class';
				} else if (matcher.isId(selector)) {
					selector = selector.replace('#', '');
					selectorType = 'id';
				}

				if (!selectorType) {
					return null;
				}

				var checkForClosestParent = function(element) {
					if (element) {
						switch (selectorType) {
							case 'tag':
								return (selector.toUpperCase() === element.tagName)
									? element
									: checkForClosestParent(element.parentElement);
								break;
							case 'class':
								return ($(element).hasClass(selector))
									? element
									: checkForClosestParent(element.parentElement);
								break;
							case 'id':
								return (element.id === selector)
									? element
									: checkForClosestParent(element.parentElement);
								break;
							default:
								break;
						}
					}
					return null;
				};

				for (var i = 0; i < this.size; i++) {
					var el = this.els[i].parentElement;

					var closestParent = checkForClosestParent(el);
					if (closestParent) {
						elements.push(closestParent);
					}
				}

				for (var i = elements.length - 1; i > 0; i--) {
					var elementToBeChecked = elements[i];
					for (var j = i-1; j >= 0; j--) {
						var elementToBeCheckedWith = elements[j];
						if (elementToBeChecked == elementToBeCheckedWith) {
							elements.pop();
							break;
						}
					}
				}

				return elements;
			},

			'find' : function(selector) {
				var list = [];
				this.each(function(el) {
					var newElement = search(el, selector);
					if(list.indexOf(newElement) === -1) {
						list = list.concat(search(el, selector));
					}
				});
				return query(list);
			},

			'first' : function() {
				return this.els[0];
			},

			'last' : function() {
				return this.els[this.size - 1];
			},

			'previous' : function() {
				var el = this.els[0];
			    if(el.previousElementSibling) {
			        return query(el.previousElementSibling);
			    } else {
			        while(el = el.previousSibling) {
			            if(el.nodeType === 1) return query(el);
			        }
			    }
			},

			'next' : function() {
				var el = this.els[0];
				if(el.nextElementSibling) {
					return query(el.nextElementSibling);
				} else {
					while(el = el.nextSibling) {
						if(el.nodeType === 1) return query(el);
					}
				}
			},

			'parent' : function() {
				var parents = [];
				for(var i = 0; i < this.size; i++) {
					parents.push(this.els[i].parentElement);
				}
				return query(parents);
			},

			'isEmpty' : function() {
				return this.els === undefined || !this.els.length;
			},

			'on' : function(eventName, command, named) {
				this.each(function(el) {
					event.addEvent(el, eventName, command, named);
				});
				return this;
			},

			'throttle' : function(eventName, command, throttleTime, named) {
				throttleTime = throttleTime || 1000;
				var previous = 0;
				var commandWithThrottle = function(event) {
					var now = + new Date();
					if(previous + throttleTime <= now) {
						command.apply(this, arguments);
						previous = + new Date();
					}
					event.preventDefault();
				}

				return this.on(eventName, commandWithThrottle, named);
			},

			'off' : function(eventName, named) {
				this.each(function(el) {
					event.removeEvent(el, eventName, named);
				});
				return this;
			},

			'selectedText' : function() {
				var input = this.els[0];
				var type = this.attr('type');
				var validSelectionStart = type? type.match('(text|search|password|tel|url)') : false;
				if(document.activeElement !== input) {
					return;
				} else if(validSelectionStart && typeof input.selectionStart !== 'undefined' && input.selectionEnd > 0) {
					return input.value.substring(input.selectionStart, input.selectionEnd);
				} else if (window.getSelection) {
					return window.getSelection().toString();
				} else if (document.getSelection) {
					return document.getSelection().toString();
				} else if (document.selection) {
					if(document.selection.createRange().text) {
						return document.selection.createRange().text;
					}
					return document.selection.createRange().htmlText;
				} else {
					return;
				}
			}
		}
	}

	return function(selector) {
		if(!selector) {
			return selector;
		} else if(typeof selector === "string") {
			return query(search(document, selector));
		}
		return query(selector);
	};
});
<|endoftext|>


<commit_before>const elm = Npm.require('elm/platform')

const elmMake = elm.executablePaths['elm-make']
const elmPackage = elm.executablePaths['elm-package']

const wrapScript = str => str + `
; if (Meteor.isServer) {
  Elm.worker(Elm.Main)
} else {
  window.addEventListener('load', function () {
    Elm.fullscreen(Elm.Main)
  })
}
`

const ignore = `elm-stuff
.tmp
.modules
`

const ElmCompiler = {}
ElmCompiler.processFilesForTarget = files => {
  const fs = Plugin.fs
  const path = Plugin.path

  const h = helpers(Plugin, Meteor)

  const setUpDirs = (root) => {
    const elmDir = `${root}/.elm`
    if(!h.exists(elmDir)) fs.mkdirSync(elmDir)
    if(!h.exists(`${elmDir}/.gitignore`)) fs.writeFileSync(`${elmDir}/.gitignore`, ignore)
    if(!h.exists(`${elmDir}/.modules`)) fs.mkdirSync(`${elmDir}/.modules`)
    if(!h.exists(`${elmDir}/.tmp`)) fs.mkdirSync(`${elmDir}/.tmp`)
    return elmDir
  }

  const root = h.findRoot()
  const elmDir = setUpDirs(root)
  const config = h.setUpElmSources(elmDir)

  const registerModule = h.cloneFile.bind(null, elmDir, '.modules')
  const registerIndex = h.cloneFile.bind(null, elmDir, '.')
  const registerTemp = h.cloneFile.bind(null, elmDir, '.tmp')

  files.forEach(file => {
    let tempFiles = []

    const filePath = file.getPathInPackage()
    const filename = path.basename(file.getPathInPackage())
    const packageName = file.getPackageName()

    if(filename === '.elm-dependencies.json') {
      h.addDeps(elmPackage, file.getContentsAsBuffer().toString(), elmDir)
      return
    }

    const virtPath = `${filePath}.js`

    let data

    if(packageName) {
      // If the file is within package
      if (h.shouldCompile(filename)) {
        const module = h.makeModuleName(packageName, true)
        const tmpSource = registerTemp(module, filePath, file, true)
        // Link the .tmp/module directory
        const sources = h.overrideSources(elmDir, [`.tmp/${module}`])
        const tmpPath = `${tmpSource}.tmp.js`
        h.execCommand(elmMake, [tmpSource, '--yes', `--output=${tmpPath}`], { cwd: elmDir })
        data = h.getAndUnlink(tmpPath)
        // Relink the normal sources
        h.overrideSources(elmDir, sources)
      } else if (h.shouldExpose(packageName)) {
        // Then register the module in a .elm/.modules elm- the modules
        // will be imporable by all other elm modules.
        if (h.isIndexModule(packageName, filePath)) {
          registerIndex(h.makeModuleName(packageName), filePath, file)
        } else {
          registerModule(h.makeModuleName(packageName), filePath, file)
        }
        return
      } else {
        // Might be an internal dep inside a package
        const module = h.makeModuleName(packageName, true)
        if(h.isNativeModule(filePath)) {
          registerTemp(module, h.getNativeModulePath(filePath), h.es5File(file))
        } else {
          registerTemp(module, filePath, file)
        }
        return
      }
    } else if (h.shouldCompile(filename)) {
      const sourcePath = `${elmDir}/../${filePath}`
      const tmpPath = `${sourcePath}.tmp.js`
      h.execCommand(elmMake, [sourcePath, '--yes', `--output=${tmpPath}`], { cwd: elmDir })
      data = h.getAndUnlink(tmpPath)
    } else if (h.isNativeModule(filePath)) {
      registerTemp(
        h.makeModuleName(packageName),
        h.getNativeModulePath(filePath),
        h.es5File(file)
      )
    } else {
      // Not in a package and shouldn't compile
      return
    }

    file.addJavaScript({
      path: virtPath,
      data: filename === 'Main.elm'
        ? wrapScript(data)
        : data,
      bare: true
    })

    try {
    } catch (e) {
      console.error(`\nError while parsing ${packageName ? packageName : ''} ${filePath}\n${e}\n`)
    }
  })
}

Plugin.registerCompiler({
  extensions: ['elm', '_.elm', 'elm.js', 'elm-dependencies.json'],
  filenames: []
}, () => Object.create(ElmCompiler))

<commit_msg>Move native modules in apps to .modules<commit_after>const elm = Npm.require('elm/platform')

const elmMake = elm.executablePaths['elm-make']
const elmPackage = elm.executablePaths['elm-package']

const wrapScript = str => str + `
; if (Meteor.isServer) {
  Elm.worker(Elm.Main)
} else {
  window.addEventListener('load', function () {
    Elm.fullscreen(Elm.Main)
  })
}
`

const ignore = `elm-stuff
.tmp
.modules
`

const ElmCompiler = {}
ElmCompiler.processFilesForTarget = files => {
  const fs = Plugin.fs
  const path = Plugin.path

  const h = helpers(Plugin, Meteor)

  const setUpDirs = (root) => {
    const elmDir = `${root}/.elm`
    if(!h.exists(elmDir)) fs.mkdirSync(elmDir)
    if(!h.exists(`${elmDir}/.gitignore`)) fs.writeFileSync(`${elmDir}/.gitignore`, ignore)
    if(!h.exists(`${elmDir}/.modules`)) fs.mkdirSync(`${elmDir}/.modules`)
    if(!h.exists(`${elmDir}/.tmp`)) fs.mkdirSync(`${elmDir}/.tmp`)
    return elmDir
  }

  const root = h.findRoot()
  const elmDir = setUpDirs(root)
  const config = h.setUpElmSources(elmDir)

  const registerModule = h.cloneFile.bind(null, elmDir, '.modules')
  const registerIndex = h.cloneFile.bind(null, elmDir, '.')
  const registerTemp = h.cloneFile.bind(null, elmDir, '.tmp')

  files.forEach(file => {
    let tempFiles = []

    const filePath = file.getPathInPackage()
    const filename = path.basename(file.getPathInPackage())
    const packageName = file.getPackageName()

    if(filename === '.elm-dependencies.json') {
      h.addDeps(elmPackage, file.getContentsAsBuffer().toString(), elmDir)
      return
    }

    const virtPath = `${filePath}.js`

    let data

    if(packageName) {
      // If the file is within package
      if (h.shouldCompile(filename)) {
        const module = h.makeModuleName(packageName, true)
        const tmpSource = registerTemp(module, filePath, file, true)
        // Link the .tmp/module directory
        const sources = h.overrideSources(elmDir, [`.tmp/${module}`])
        const tmpPath = `${tmpSource}.tmp.js`
        h.execCommand(elmMake, [tmpSource, '--yes', `--output=${tmpPath}`], { cwd: elmDir })
        data = h.getAndUnlink(tmpPath)
        // Relink the normal sources
        h.overrideSources(elmDir, sources)
      } else if (h.shouldExpose(packageName)) {
        // Then register the module in a .elm/.modules elm- the modules
        // will be imporable by all other elm modules.
        if (h.isIndexModule(packageName, filePath)) {
          registerIndex(h.makeModuleName(packageName), filePath, file)
        } else {
          registerModule(h.makeModuleName(packageName), filePath, file)
        }
        return
      } else {
        // Might be an internal dep inside a package
        const module = h.makeModuleName(packageName, true)
        if(h.isNativeModule(filePath)) {
          registerTemp(module, h.getNativeModulePath(filePath), h.es5File(file))
        } else {
          registerTemp(module, filePath, file)
        }
        return
      }
    } else if (h.shouldCompile(filename)) {
      const sourcePath = `${elmDir}/../${filePath}`
      const tmpPath = `${sourcePath}.tmp.js`
      h.execCommand(elmMake, [sourcePath, '--yes', `--output=${tmpPath}`], { cwd: elmDir })
      data = h.getAndUnlink(tmpPath)
    } else if (h.isNativeModule(filePath)) {
      registerModule(
        h.makeModuleName(packageName),
        h.getNativeModulePath(filePath),
        h.es5File(file)
      )
    } else {
      // Not in a package and shouldn't compile
      return
    }

    file.addJavaScript({
      path: virtPath,
      data: filename === 'Main.elm'
        ? wrapScript(data)
        : data,
      bare: true
    })

    try {
    } catch (e) {
      console.error(`\nError while parsing ${packageName ? packageName : ''} ${filePath}\n${e}\n`)
    }
  })
}

Plugin.registerCompiler({
  extensions: ['elm', '_.elm', 'elm.js', 'elm-dependencies.json'],
  filenames: []
}, () => Object.create(ElmCompiler))

<|endoftext|>


<commit_before>"use strict";

var webglew = require("webglew")
var createTexture = require("gl-texture2d")

module.exports = createFBO

var colorAttachmentArrays = null
var FRAMEBUFFER_UNSUPPORTED
var FRAMEBUFFER_INCOMPLETE_ATTACHMENT
var FRAMEBUFFER_INCOMPLETE_DIMENSIONS
var FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT

function saveFBOState(gl) {
  var fbo = gl.getParameter(gl.FRAMEBUFFER_BINDING)
  var rbo = gl.getParameter(gl.RENDERBUFFER_BINDING)
  var tex = gl.getParameter(gl.TEXTURE_BINDING_2D)
  return [fbo, rbo, tex]
}

function restoreFBOState(gl, data) {
  gl.bindFramebuffer(gl.FRAMEBUFFER, data[0])
  gl.bindRenderbuffer(gl.RENDERBUFFER, data[1])
  gl.bindTexture(gl.TEXUTRE_2D, data[2])
}

function lazyInitColorAttachments(gl, ext) {
  var maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL);
  colorAttachmentArrays = new Array(maxColorAttachments + 1)
  for(var i=0; i<=maxColorAttachments; ++i) {
    var x = new Array(maxColorAttachments)
    for(var j=0; j<i; ++j) {
      x[j] = gl.COLOR_ATTACHMENT0 + j
    }
    for(var j=i; j<maxColorAttachments; ++j) {
      x[j] = gl.NONE
    }
    colorAttachmentArrays[i] = x
  }
}

//Throw an appropriate error
function throwFBOError(status) {
  switch(status){
    case FRAMEBUFFER_UNSUPPORTED:
      throw new Error("gl-fbo: Framebuffer unsupported")
    case FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
      throw new Error("gl-fbo: Framebuffer incomplete attachment")
    case FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
      throw new Error("gl-fbo: Framebuffer incomplete dimensions")
    case FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
      throw new Error("gl-fbo: Framebuffer incomplete missing attachment")
    default:
      throw new Error("gl-fbo: Framebuffer failed for unspecified reason")
  }
}

//Initialize a texture object
function initTexture(gl, width, height, type, format, attachment) {
  if(!type) {
    return null
  }
  var result = createTexture(gl, width, height, format, type)
  result.magFilter = gl.NEAREST
  result.minFilter = gl.NEAREST
  result.mipSamples = 1
  result.bind()
  gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, result.handle, 0)
  return result
}

//Initialize a render buffer object
function initRenderBuffer(gl, width, height, component, attachment) {
  var result = gl.createRenderbuffer()
  gl.bindRenderbuffer(gl.RENDERBUFFER, result)
  gl.renderbufferStorage(gl.RENDERBUFFER, component, width, height)
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, result)
  return result
}

//Rebuild the frame buffer
function rebuildFBO(fbo) {

  //Save FBO state
  var state = saveFBOState(fbo.gl)

  var gl = fbo.gl
  var handle = fbo.handle = gl.createFramebuffer()
  var width = fbo._shape[0]
  var height = fbo._shape[1]
  var numColors = fbo.color.length
  var ext = fbo._ext
  var useStencil = fbo._useStencil
  var useDepth = fbo._useDepth
  var colorType = fbo._colorType
  var extensions = webglew(gl)

  //Bind the fbo
  gl.bindFramebuffer(gl.FRAMEBUFFER, handle)

  //Allocate color buffers
  for(var i=0; i<numColors; ++i) {
    fbo.color[i] = initTexture(gl, width, height, colorType, gl.RGBA, gl.COLOR_ATTACHMENT0 + i)
  }
  if(numColors === 0) {
    fbo._color_rb = initRenderBuffer(gl, width, height, gl.RGBA4, gl.COLOR_ATTACHMENT0)
    if(ext) {
      ext.drawBuffersWEBGL(colorAttachmentArrays[0])
    }
  } else if(numColors > 1) {
    ext.drawBuffersWEBGL(colorAttachmentArrays[numColor])
  }

  //Allocate depth/stencil buffers
  if(extensions.WEBGL_depth_texture) {
    if(useStencil) {
      fbo.depth = initTexture(gl, width, height,
                          extensions.WEBGL_depth_texture.UNSIGNED_INT_24_8_WEBGL,
                          gl.DEPTH_STENCIL,
                          gl.DEPTH_STENCIL_ATTACHMENT)
    } else if(useDepth) {
      fbo.depth = initTexture(gl, width, height,
                          gl.UNSIGNED_SHORT,
                          gl.DEPTH_COMPONENT,
                          gl.DEPTH_ATTACHMENT)
    }
  } else {
    if(useDepth && useStencil) {
      fbo._depth_rb = initRenderBuffer(gl, width, height, gl.DEPTH_STENCIL, gl.DEPTH_STENCIL_ATTACHMENT)
    } else if(useDepth) {
      fbo._depth_rb = initRenderBuffer(gl, width, height, gl.DEPTH_COMPONENT16, gl.DEPTH_ATTACHMENT)
    } else if(useStencil) {
      fbo._depth_rb = initRenderBuffer(gl, width, height, gl.STENCIL_INDEX, gl.STENCIL_ATTACHMENT)
    }
  }

  //Check frame buffer state
  var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
  if(status !== gl.FRAMEBUFFER_COMPLETE) {

    //Release all partially allocated resources
    fbo._destroyed = true

    //Release all resources
    gl.bindFramebuffer(gl.FRAMEBUFFER, null)
    gl.deleteFramebuffer(fbo.handle)
    fbo.handle = null
    if(fbo.depth) {
      fbo.depth.dispose()
      fbo.depth = null
    }
    if(fbo._depth_rb) {
      gl.deleteRenderbuffer(fbo._depth_rb)
      fbo._depth_rb = null
    }
    for(var i=0; i<fbo.color.length; ++i) {
      fbo.color[i].dispose()
      fbo.color[i] = null
    }
    if(fbo._color_rb) {
      gl.deleteRenderbuffer(fbo._color_rb)
      fbo._color_rb = null
    }

    restoreFBOState(gl, state)

    //Throw the frame buffer error
    throwFBOError(status)
  }

  //Everything ok, let's get on with life
  restoreFBOState(gl, state)
}

function Framebuffer(gl, width, height, colorType, numColors, useDepth, useStencil, ext) {
  var extensions = webglew(gl)

  //Handle and set properties
  this.gl = gl
  this._shape = [width|0, height|0]
  this._destroyed = false
  this._ext = ext

  //Allocate buffers
  this.color = new Array(numColors)
  for(var i=0; i<numColors; ++i) {
    this.color[i] = null
  }
  this._color_rb = null
  this.depth = null
  this._depth_rb = null

  //Save depth and stencil flags
  this._colorType = colorType
  this._useDepth = useDepth
  this._useStencil = useStencil

  //Shape vector for resizing
  var parent = this
  var shapeVector = [width|0, height|0]
  Object.defineProperties(shapeVector, {
    0: {
      get: function() {
        return parent._shape[0]
      },
      set: function(w) {
        return parent.width = w
      }
    },
    1: {
      get: function() {
        return parent._shape[1]
      },
      set: function(h) {
        return parent.height = h
      }
    }
  })
  this._shapeVector = shapeVector

  //Initialize all attachments
  rebuildFBO(this)
}

var proto = Framebuffer.prototype

function reshapeFBO(fbo, w, h) {
  //If fbo is invalid, just skip this
  if(fbo._destroyed) {
    throw new Error("gl-fbo: Can't resize destroyed FBO")
  }

  //Don't resize if no change in shape
  if( (fbo._shape[0] === w) &&
      (fbo._shape[1] === h) ) {
    return
  }

  var gl = fbo.gl

  //Check parameter ranges
  var maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE)
  if( w < 0 || w > maxFBOSize ||
      h < 0 || h > maxFBOSize) {
    throw new Error("gl-fbo: Can't resize FBO, invalid dimensions")
  }

  //Update shape
  fbo._shape[0] = w
  fbo._shape[1] = h

  //Save framebuffer state
  var state = saveFBOState(gl)

  //Resize framebuffer attachments
  for(var i=0; i<fbo.color.length; ++i) {
    fbo.color[i].shape = fbo._shape
  }
  if(fbo._color_rb) {
    gl.bindRenderbuffer(gl.RENDERBUFFER, fbo._color_rb)
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, fbo._shape[0], fbo._shape[1])
  }
  if(fbo.depth) {
    fbo.depth.shape = fbo._shape
  }
  if(fbo._depth_rb) {
    gl.bindRenderbuffer(gl.RENDERBUFFER, fbo._depth_rb)
    if(fbo._useDepth && fbo._useStencil) {
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, fbo._shape[0], fbo._shape[1])
    } else if(fbo._useDepth) {
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, fbo._shape[0], fbo._shape[1])
    } else if(fbo._useStencil) {
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX, fbo._shape[0], fbo._shape[1])
    }
  }

  //Check FBO status after resize, if something broke then die in a fire
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.handle)
  var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
  if(status !== gl.FRAMEBUFFER_COMPLETE) {
    fbo.dispose()
    restoreFBOState(gl, state)
    throwFBOError(status)
  }

  //Restore framebuffer state
  restoreFBOState(gl, state)
}

Object.defineProperties(proto, {
  "shape": {
    get: function() {
      if(this._destroyed) {
        return [0,0]
      }
      return this._shapeVector
    },
    set: function(x) {
      if(!Array.isArray(x)) {
        x = [x|0, x|0]
      }
      if(x.length !== 2) {
        throw new Error("gl-fbo: Shape vector must be length 2")
      }

      var w = x[0]|0
      var h = x[1]|0
      reshapeFBO(this, w, h)

      return [w, h]
    },
    enumerable: false
  },
  "width": {
    get: function() {
      if(this._destroyed) {
        return 0
      }
      return this._shape[0]
    },
    set: function(w) {
      w = w|0
      reshapeFBO(this, w, this._shape[1])
      return w
    },
    enumerable: false
  },
  "height": {
    get: function() {
      if(this._destroyed) {
        return 0
      }
      return this._shape[1]
    },
    set: function(h) {
      h = h|0
      reshapeFBO(this, this._shape[0], h)
      return h
    },
    enumerable: false
  }
})

proto.bind = function() {
  if(this._destroyed) {
    return
  }
  var gl = this.gl
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.handle)
  gl.viewport(0, 0, this._shape[0], this._shape[1])
}

proto.dispose = function() {
  if(this._destroyed) {
    return
  }
  this._destroyed = true
  var gl = this.gl
  gl.deleteFramebuffer(this.handle)
  this.handle = null
  if(this.depth) {
    this.depth.dispose()
    this.depth = null
  }
  if(this._depth_rb) {
    gl.deleteRenderbuffer(this._depth_rb)
    this._depth_rb = null
  }
  for(var i=0; i<this.color.length; ++i) {
    this.color[i].dispose()
    this.color[i] = null
  }
  if(this._color_rb) {
    gl.deleteRenderbuffer(this._color_rb)
    this._color_rb = null
  }
}

function createFBO(gl, width, height, options) {

  //Update frame buffer error code values
  if(!FRAMEBUFFER_UNSUPPORTED) {
    FRAMEBUFFER_UNSUPPORTED = gl.FRAMEBUFFER_UNSUPPORTED
    FRAMEBUFFER_INCOMPLETE_ATTACHMENT = gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT
    FRAMEBUFFER_INCOMPLETE_DIMENSIONS = gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS
    FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
  }

  var extensions = webglew(gl)

  //Lazily initialize color attachment arrays
  if(!colorAttachmentArrays && extensions.WEBGL_draw_buffers) {
    lazyInitColorAttachments(gl, extensions.WEBGL_draw_buffers)
  }

  //Special case: Can accept an array as argument
  if(Array.isArray(width)) {
    options = height
    height = width[1]|0
    width = width[0]|0
  }

  if(typeof width !== "number") {
    throw new Error("gl-fbo: Missing shape parameter")
  }

  //Validate width/height properties
  var maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE)
  if(width < 0 || width > maxFBOSize || height < 0 || height > maxFBOSize) {
    throw new Error("gl-fbo: Parameters are too large for FBO")
  }

  //Handle each option type
  options = options || {}

  //Figure out number of color buffers to use
  var numColors = 1
  if("color" in options) {
    numColors = Math.max(options.color|0, 0)
    if(numColors < 0) {
      throw new Error("gl-fbo: Must specify a nonnegative number of colors")
    }
    if(numColors > 1) {
      //Check if multiple render targets supported
      var mrtext = extensions.WEBGL_draw_buffers
      if(!mrtext) {
        throw new Error("gl-fbo: Multiple draw buffer extension not supported")
      } else if(numColors > gl.getParameter(mrtext.MAX_COLOR_ATTACHMENTS_WEBGL)) {
        throw new Error("gl-fbo: Context does not support " + numColors + " draw buffers")
      }
    }
  }

  //Determine whether to use floating point textures
  var colorType = gl.UNSIGNED_BYTE
  if(options.float && numColors > 0) {
    if(!extensions.OES_texture_float) {
      throw new Error("gl-fbo: Context does not support floating point textures")
    }
    colorType = gl.FLOAT
  } else if(options.preferFloat && numColors > 0) {
    if(extensions.OES_texture_float) {
      colorType = gl.FLOAT
    }
  }

  //Check if we should use depth buffer
  var useDepth = true
  if("depth" in options) {
    useDepth = !!options.depth
  }

  //Check if we should use a stencil buffer
  var useStencil = false
  if("stencil" in options) {
    useStencil = !!options.stencil
  }

  return new Framebuffer(
    gl,
    width,
    height,
    colorType,
    numColors,
    useDepth,
    useStencil,
    extensions.WEBGL_draw_buffers)
}<commit_msg>fix up null warning for gl.bindTexture<commit_after>"use strict";

var webglew = require("webglew")
var createTexture = require("gl-texture2d")

module.exports = createFBO

var colorAttachmentArrays = null
var FRAMEBUFFER_UNSUPPORTED
var FRAMEBUFFER_INCOMPLETE_ATTACHMENT
var FRAMEBUFFER_INCOMPLETE_DIMENSIONS
var FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT

function saveFBOState(gl) {
  var fbo = gl.getParameter(gl.FRAMEBUFFER_BINDING)
  var rbo = gl.getParameter(gl.RENDERBUFFER_BINDING)
  var tex = gl.getParameter(gl.TEXTURE_BINDING_2D)
  return [fbo, rbo, tex]
}

function restoreFBOState(gl, data) {
  gl.bindFramebuffer(gl.FRAMEBUFFER, data[0])
  gl.bindRenderbuffer(gl.RENDERBUFFER, data[1])
  gl.bindTexture(gl.TEXUTRE_2D, data[2] || null)
}

function lazyInitColorAttachments(gl, ext) {
  var maxColorAttachments = gl.getParameter(ext.MAX_COLOR_ATTACHMENTS_WEBGL);
  colorAttachmentArrays = new Array(maxColorAttachments + 1)
  for(var i=0; i<=maxColorAttachments; ++i) {
    var x = new Array(maxColorAttachments)
    for(var j=0; j<i; ++j) {
      x[j] = gl.COLOR_ATTACHMENT0 + j
    }
    for(var j=i; j<maxColorAttachments; ++j) {
      x[j] = gl.NONE
    }
    colorAttachmentArrays[i] = x
  }
}

//Throw an appropriate error
function throwFBOError(status) {
  switch(status){
    case FRAMEBUFFER_UNSUPPORTED:
      throw new Error("gl-fbo: Framebuffer unsupported")
    case FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
      throw new Error("gl-fbo: Framebuffer incomplete attachment")
    case FRAMEBUFFER_INCOMPLETE_DIMENSIONS:
      throw new Error("gl-fbo: Framebuffer incomplete dimensions")
    case FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
      throw new Error("gl-fbo: Framebuffer incomplete missing attachment")
    default:
      throw new Error("gl-fbo: Framebuffer failed for unspecified reason")
  }
}

//Initialize a texture object
function initTexture(gl, width, height, type, format, attachment) {
  if(!type) {
    return null
  }
  var result = createTexture(gl, width, height, format, type)
  result.magFilter = gl.NEAREST
  result.minFilter = gl.NEAREST
  result.mipSamples = 1
  result.bind()
  gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, gl.TEXTURE_2D, result.handle, 0)
  return result
}

//Initialize a render buffer object
function initRenderBuffer(gl, width, height, component, attachment) {
  var result = gl.createRenderbuffer()
  gl.bindRenderbuffer(gl.RENDERBUFFER, result)
  gl.renderbufferStorage(gl.RENDERBUFFER, component, width, height)
  gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, result)
  return result
}

//Rebuild the frame buffer
function rebuildFBO(fbo) {

  //Save FBO state
  var state = saveFBOState(fbo.gl)

  var gl = fbo.gl
  var handle = fbo.handle = gl.createFramebuffer()
  var width = fbo._shape[0]
  var height = fbo._shape[1]
  var numColors = fbo.color.length
  var ext = fbo._ext
  var useStencil = fbo._useStencil
  var useDepth = fbo._useDepth
  var colorType = fbo._colorType
  var extensions = webglew(gl)

  //Bind the fbo
  gl.bindFramebuffer(gl.FRAMEBUFFER, handle)

  //Allocate color buffers
  for(var i=0; i<numColors; ++i) {
    fbo.color[i] = initTexture(gl, width, height, colorType, gl.RGBA, gl.COLOR_ATTACHMENT0 + i)
  }
  if(numColors === 0) {
    fbo._color_rb = initRenderBuffer(gl, width, height, gl.RGBA4, gl.COLOR_ATTACHMENT0)
    if(ext) {
      ext.drawBuffersWEBGL(colorAttachmentArrays[0])
    }
  } else if(numColors > 1) {
    ext.drawBuffersWEBGL(colorAttachmentArrays[numColor])
  }

  //Allocate depth/stencil buffers
  if(extensions.WEBGL_depth_texture) {
    if(useStencil) {
      fbo.depth = initTexture(gl, width, height,
                          extensions.WEBGL_depth_texture.UNSIGNED_INT_24_8_WEBGL,
                          gl.DEPTH_STENCIL,
                          gl.DEPTH_STENCIL_ATTACHMENT)
    } else if(useDepth) {
      fbo.depth = initTexture(gl, width, height,
                          gl.UNSIGNED_SHORT,
                          gl.DEPTH_COMPONENT,
                          gl.DEPTH_ATTACHMENT)
    }
  } else {
    if(useDepth && useStencil) {
      fbo._depth_rb = initRenderBuffer(gl, width, height, gl.DEPTH_STENCIL, gl.DEPTH_STENCIL_ATTACHMENT)
    } else if(useDepth) {
      fbo._depth_rb = initRenderBuffer(gl, width, height, gl.DEPTH_COMPONENT16, gl.DEPTH_ATTACHMENT)
    } else if(useStencil) {
      fbo._depth_rb = initRenderBuffer(gl, width, height, gl.STENCIL_INDEX, gl.STENCIL_ATTACHMENT)
    }
  }

  //Check frame buffer state
  var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
  if(status !== gl.FRAMEBUFFER_COMPLETE) {

    //Release all partially allocated resources
    fbo._destroyed = true

    //Release all resources
    gl.bindFramebuffer(gl.FRAMEBUFFER, null)
    gl.deleteFramebuffer(fbo.handle)
    fbo.handle = null
    if(fbo.depth) {
      fbo.depth.dispose()
      fbo.depth = null
    }
    if(fbo._depth_rb) {
      gl.deleteRenderbuffer(fbo._depth_rb)
      fbo._depth_rb = null
    }
    for(var i=0; i<fbo.color.length; ++i) {
      fbo.color[i].dispose()
      fbo.color[i] = null
    }
    if(fbo._color_rb) {
      gl.deleteRenderbuffer(fbo._color_rb)
      fbo._color_rb = null
    }

    restoreFBOState(gl, state)

    //Throw the frame buffer error
    throwFBOError(status)
  }

  //Everything ok, let's get on with life
  restoreFBOState(gl, state)
}

function Framebuffer(gl, width, height, colorType, numColors, useDepth, useStencil, ext) {
  var extensions = webglew(gl)

  //Handle and set properties
  this.gl = gl
  this._shape = [width|0, height|0]
  this._destroyed = false
  this._ext = ext

  //Allocate buffers
  this.color = new Array(numColors)
  for(var i=0; i<numColors; ++i) {
    this.color[i] = null
  }
  this._color_rb = null
  this.depth = null
  this._depth_rb = null

  //Save depth and stencil flags
  this._colorType = colorType
  this._useDepth = useDepth
  this._useStencil = useStencil

  //Shape vector for resizing
  var parent = this
  var shapeVector = [width|0, height|0]
  Object.defineProperties(shapeVector, {
    0: {
      get: function() {
        return parent._shape[0]
      },
      set: function(w) {
        return parent.width = w
      }
    },
    1: {
      get: function() {
        return parent._shape[1]
      },
      set: function(h) {
        return parent.height = h
      }
    }
  })
  this._shapeVector = shapeVector

  //Initialize all attachments
  rebuildFBO(this)
}

var proto = Framebuffer.prototype

function reshapeFBO(fbo, w, h) {
  //If fbo is invalid, just skip this
  if(fbo._destroyed) {
    throw new Error("gl-fbo: Can't resize destroyed FBO")
  }

  //Don't resize if no change in shape
  if( (fbo._shape[0] === w) &&
      (fbo._shape[1] === h) ) {
    return
  }

  var gl = fbo.gl

  //Check parameter ranges
  var maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE)
  if( w < 0 || w > maxFBOSize ||
      h < 0 || h > maxFBOSize) {
    throw new Error("gl-fbo: Can't resize FBO, invalid dimensions")
  }

  //Update shape
  fbo._shape[0] = w
  fbo._shape[1] = h

  //Save framebuffer state
  var state = saveFBOState(gl)

  //Resize framebuffer attachments
  for(var i=0; i<fbo.color.length; ++i) {
    fbo.color[i].shape = fbo._shape
  }
  if(fbo._color_rb) {
    gl.bindRenderbuffer(gl.RENDERBUFFER, fbo._color_rb)
    gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA4, fbo._shape[0], fbo._shape[1])
  }
  if(fbo.depth) {
    fbo.depth.shape = fbo._shape
  }
  if(fbo._depth_rb) {
    gl.bindRenderbuffer(gl.RENDERBUFFER, fbo._depth_rb)
    if(fbo._useDepth && fbo._useStencil) {
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_STENCIL, fbo._shape[0], fbo._shape[1])
    } else if(fbo._useDepth) {
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, fbo._shape[0], fbo._shape[1])
    } else if(fbo._useStencil) {
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.STENCIL_INDEX, fbo._shape[0], fbo._shape[1])
    }
  }

  //Check FBO status after resize, if something broke then die in a fire
  gl.bindFramebuffer(gl.FRAMEBUFFER, fbo.handle)
  var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER)
  if(status !== gl.FRAMEBUFFER_COMPLETE) {
    fbo.dispose()
    restoreFBOState(gl, state)
    throwFBOError(status)
  }

  //Restore framebuffer state
  restoreFBOState(gl, state)
}

Object.defineProperties(proto, {
  "shape": {
    get: function() {
      if(this._destroyed) {
        return [0,0]
      }
      return this._shapeVector
    },
    set: function(x) {
      if(!Array.isArray(x)) {
        x = [x|0, x|0]
      }
      if(x.length !== 2) {
        throw new Error("gl-fbo: Shape vector must be length 2")
      }

      var w = x[0]|0
      var h = x[1]|0
      reshapeFBO(this, w, h)

      return [w, h]
    },
    enumerable: false
  },
  "width": {
    get: function() {
      if(this._destroyed) {
        return 0
      }
      return this._shape[0]
    },
    set: function(w) {
      w = w|0
      reshapeFBO(this, w, this._shape[1])
      return w
    },
    enumerable: false
  },
  "height": {
    get: function() {
      if(this._destroyed) {
        return 0
      }
      return this._shape[1]
    },
    set: function(h) {
      h = h|0
      reshapeFBO(this, this._shape[0], h)
      return h
    },
    enumerable: false
  }
})

proto.bind = function() {
  if(this._destroyed) {
    return
  }
  var gl = this.gl
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.handle)
  gl.viewport(0, 0, this._shape[0], this._shape[1])
}

proto.dispose = function() {
  if(this._destroyed) {
    return
  }
  this._destroyed = true
  var gl = this.gl
  gl.deleteFramebuffer(this.handle)
  this.handle = null
  if(this.depth) {
    this.depth.dispose()
    this.depth = null
  }
  if(this._depth_rb) {
    gl.deleteRenderbuffer(this._depth_rb)
    this._depth_rb = null
  }
  for(var i=0; i<this.color.length; ++i) {
    this.color[i].dispose()
    this.color[i] = null
  }
  if(this._color_rb) {
    gl.deleteRenderbuffer(this._color_rb)
    this._color_rb = null
  }
}

function createFBO(gl, width, height, options) {

  //Update frame buffer error code values
  if(!FRAMEBUFFER_UNSUPPORTED) {
    FRAMEBUFFER_UNSUPPORTED = gl.FRAMEBUFFER_UNSUPPORTED
    FRAMEBUFFER_INCOMPLETE_ATTACHMENT = gl.FRAMEBUFFER_INCOMPLETE_ATTACHMENT
    FRAMEBUFFER_INCOMPLETE_DIMENSIONS = gl.FRAMEBUFFER_INCOMPLETE_DIMENSIONS
    FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = gl.FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT
  }

  var extensions = webglew(gl)

  //Lazily initialize color attachment arrays
  if(!colorAttachmentArrays && extensions.WEBGL_draw_buffers) {
    lazyInitColorAttachments(gl, extensions.WEBGL_draw_buffers)
  }

  //Special case: Can accept an array as argument
  if(Array.isArray(width)) {
    options = height
    height = width[1]|0
    width = width[0]|0
  }

  if(typeof width !== "number") {
    throw new Error("gl-fbo: Missing shape parameter")
  }

  //Validate width/height properties
  var maxFBOSize = gl.getParameter(gl.MAX_RENDERBUFFER_SIZE)
  if(width < 0 || width > maxFBOSize || height < 0 || height > maxFBOSize) {
    throw new Error("gl-fbo: Parameters are too large for FBO")
  }

  //Handle each option type
  options = options || {}

  //Figure out number of color buffers to use
  var numColors = 1
  if("color" in options) {
    numColors = Math.max(options.color|0, 0)
    if(numColors < 0) {
      throw new Error("gl-fbo: Must specify a nonnegative number of colors")
    }
    if(numColors > 1) {
      //Check if multiple render targets supported
      var mrtext = extensions.WEBGL_draw_buffers
      if(!mrtext) {
        throw new Error("gl-fbo: Multiple draw buffer extension not supported")
      } else if(numColors > gl.getParameter(mrtext.MAX_COLOR_ATTACHMENTS_WEBGL)) {
        throw new Error("gl-fbo: Context does not support " + numColors + " draw buffers")
      }
    }
  }

  //Determine whether to use floating point textures
  var colorType = gl.UNSIGNED_BYTE
  if(options.float && numColors > 0) {
    if(!extensions.OES_texture_float) {
      throw new Error("gl-fbo: Context does not support floating point textures")
    }
    colorType = gl.FLOAT
  } else if(options.preferFloat && numColors > 0) {
    if(extensions.OES_texture_float) {
      colorType = gl.FLOAT
    }
  }

  //Check if we should use depth buffer
  var useDepth = true
  if("depth" in options) {
    useDepth = !!options.depth
  }

  //Check if we should use a stencil buffer
  var useStencil = false
  if("stencil" in options) {
    useStencil = !!options.stencil
  }

  return new Framebuffer(
    gl,
    width,
    height,
    colorType,
    numColors,
    useDepth,
    useStencil,
    extensions.WEBGL_draw_buffers)
}<|endoftext|>


<commit_before>var HEIGHT = 550;
var WIDTH = 600;
var DEFAULT_COLOR = ["#FFFFFF", "#FF0000", "#00FF00", "#0000FF", "#FFFF00"];
var LEVEL = [
	{
		lines: [
			[0,HEIGHT/2, WIDTH,HEIGHT/2],
			[WIDTH/2,0, WIDTH/2,HEIGHT]
		],
		parts: [
			[0,0, WIDTH/2,HEIGHT/2],
			[WIDTH/2,0, WIDTH,HEIGHT/2],
			[0,HEIGHT/2, WIDTH/2,HEIGHT],
			[WIDTH/2,HEIGHT/2, WIDTH,HEIGHT]
		],
		neighbours: [
			[0, 1, 1, 1],
			[1, 0, 1, 1],
			[1, 1, 0, 1],
			[1, 1, 1, 0]
		]
	}
]
function getElmt(id){ return document.getElementById(id); }
function FCG(){
	this.cdom = getElmt('myfcg');
	this.ctx = this.cdom.getContext('2d');
	this.currentColor = 0;
	this.currentLevel = 0;
}
FCG.prototype.line = function(x1,y1, x2,y2){
	this.ctx.moveTo(x1,y1);
	this.ctx.lineTo(x2,y2);
	this.ctx.lineWidth = 1;
	this.ctx.strokeStyle = '#bbb';
	this.ctx.stroke();
}
FCG.prototype.drawLines = function(lines){
	var line;
	for (var i=0; i<lines.length; i++){
		line = lines[i];
		this.line(line[0],line[1], line[2],line[3]);
	}
}
FCG.prototype.setColor = function (index){ this.currentColor = index; }
FCG.prototype.fillPart = function (index){
	var parts = LEVEL[this.currentLevel].parts;
	this.ctx.fillStyle = DEFAULT_COLOR[this.currentColor];
	this.ctx.fillRect(parts[index][0]+1, parts[index][1]+1, parts[index][2]-1,parts[index][3]-1);
	this.partsColor[index] = this.currentColor;
}
FCG.prototype.setLevelText = function (){ var lvl = getElmt('level'); lvl.innerText = this.currentLevel+1; }
FCG.prototype.initPartsColor = function(){
	var parts = LEVEL[this.currentLevel].parts
	this.partsColor = new Array();

	for (var i=0; i<parts.length; i++){
		this.partsColor[i] = 0;
	}
}
FCG.prototype.start = function (){
	var level = LEVEL[this.currentLevel];

	this.currentColor = 0;
	this.setLevelText();
	this.initPartsColor();
	this.drawLines(level.lines);
	for (var i=0; i<this.partsColor.length; i++){
		this.fillPart(i);
	}
}
FCG.prototype.next = function(){
	var max = LEVEL.length - 1;

	if (max == this.currentLevel){
		this.currentLevel = 0;

		this.win();
	} else if (max > this.currentLevel){
		this.currentLevel++;
		this.start();
	}
}
FCG.prototype.win = function(){
	alert('You are the winner');
	this.start();
}
FCG.prototype.validate = function(){
	var valid = true;
	var neighbours = LEVEL[this.currentLevel].neighbours

	for (var i=0; i<this.partsColor.length; i++){
		if (this.partsColor[i] == 0){
			valid = false;
			break;
		}
	}
	if (valid){
		for (var i=0; i<neighbours.length; i++){
			for (var j=0; j<neighbours[i].length; j++){
				if ((neighbours[i][j] == 1) && (this.partsColor[i] == this.partsColor[j])){
					valid = false;
					break;
				}
			}
			if (!valid) break;
		}
	}
	return valid;
}
FCG.prototype.calculatePosition = function(event){
	var x = event.x;
	var y = event.y;
	console.log('original click', x + ',' + y);
	console.log('offsetLeft', this.cdom.offsetLeft);
	console.log('offsetTop', this.cdom.offsetTop);
	x = x - this.cdom.offsetLeft + window.scrollX;
	y = y - this.cdom.offsetTop + window.scrollY;
	return [x,y];
}
FCG.prototype.getPartByPoint = function(position){
	var result = -1;
	var parts = LEVEL[this.currentLevel].parts;
	var part;

	for (var i=0; i<parts.length; i++){
		part = new Rect(parts[i][0],parts[i][1], parts[i][2],parts[i][3]);
		if (part.isInside(position[0],position[1])){
			result = i;
			break;
		}
	}
	return result;
}
function Rect(x1,y1, x2,y2){
	this.x1 = x1;
	this.y1 = y1;
	this.x2 = x2;
	this.y2 = y2;
}
Rect.prototype.isInside = function(x, y){
	return x >= this.x1 && x <= this.x2 && y >= this.y1 && y <= this.y2;
}
function startGame(){
	fcg = new FCG();
	fcg.start();
	var btn = getElmt('validate');
	var reset = getElmt('reset');
	var red = getElmt('red');
	var green = getElmt('green');
	var blue = getElmt('blue');
	var yellow = getElmt('yellow');
	btn.addEventListener("click", function(){
		valid = fcg.validate();
		console.log('valid', valid);
		if (valid){
			fcg.next();
		} else {
			alert("Invalid fill.\nAll parts must be filled with no default color.\nNeighbouring parts must be filled with different color.");
		}
	});
	red.addEventListener('click', function(){
		fcg.currentColor = 1;
	});
	green.addEventListener('click', function(){
		fcg.currentColor = 2;
	});
	blue.addEventListener('click', function(){
		fcg.currentColor = 3;
	});
	yellow.addEventListener('click', function(){
		fcg.currentColor = 4;
	});
	reset.addEventListener('click', function(){
		var input = confirm("Are you sure want to clear all parts at this level?");
		if (input == true){
			fcg.start();
		}
	});
	fcg.cdom.addEventListener("mousedown", function (event){
		var clickPos = fcg.calculatePosition(event);
		console.log('Calibrated position', clickPos[0] + ',' + clickPos[1]);
		var partNumber = fcg.getPartByPoint(clickPos);
		console.log('click in area', partNumber);
		fcg.fillPart(partNumber);
	}, false);
}<commit_msg>Added level 2<commit_after>var HEIGHT = 550;
var WIDTH = 600;
var DEFAULT_COLOR = ["#000000", "#FF0000", "#00FF00", "#0000FF", "#FFFF00"];
var LEVEL = [
	{
		lines: [
			[0, HEIGHT/2, WIDTH, HEIGHT/2],
			[WIDTH/2, 0, WIDTH/2, HEIGHT]
		],
		parts: [
			[0, 0, WIDTH/2, HEIGHT/2],
			[WIDTH/2, 0, WIDTH/2, HEIGHT/2],
			[0, HEIGHT/2, WIDTH/2, HEIGHT/2],
			[WIDTH/2, HEIGHT/2, WIDTH/2, HEIGHT/2]
		],
		neighbours: [
			[0, 1, 1, 1],
			[1, 0, 1, 1],
			[1, 1, 0, 1],
			[1, 1, 1, 0]
		]
	},{
		lines: [
			[0,HEIGHT/2, WIDTH,HEIGHT/2],
			[WIDTH/2,0, WIDTH/2,HEIGHT],
			[0,HEIGHT/4, WIDTH/2,HEIGHT/4],
			[WIDTH/4,0, WIDTH/4,HEIGHT/2],
		],
		parts: [
			[0, 0, WIDTH/4, HEIGHT/4],
			[WIDTH/4, 0, WIDTH/4, HEIGHT/4],
			[0, HEIGHT/4, WIDTH/4, HEIGHT/4],
			[WIDTH/4, HEIGHT/4, WIDTH/4, HEIGHT/4],
			[WIDTH/2, 0, WIDTH/2, HEIGHT/2],
			[0, HEIGHT/2, WIDTH/2, HEIGHT/2],
			[WIDTH/2, HEIGHT/2, WIDTH/2, HEIGHT/2]
		],
		neighbours: [
			[0, 1, 1, 1, 0, 0, 0],
			[1, 0, 1, 1, 5, 0, 0],
			[1, 1, 0, 1, 0, 1, 0],
			[1, 1, 1, 0, 1, 1, 1],
			[0, 1, 0, 1, 0, 1, 1],
			[0, 0, 1, 1, 1, 0, 1],
			[0, 0, 0, 1, 1, 1, 0],
		]
	}
]
function getElmt(id){ return document.getElementById(id); }
function FCG(){
	this.cdom = getElmt('myfcg');
	this.ctx = this.cdom.getContext('2d');
	this.currentLevel = 1;
}
FCG.prototype.line = function(x1,y1, x2,y2){
	this.ctx.moveTo(x1,y1);
	this.ctx.lineTo(x2,y2);
	this.ctx.lineWidth = 1;
	this.ctx.strokeStyle = '#bbb';
	this.ctx.stroke();
}
FCG.prototype.drawLines = function(lines){
	var line;
	for (var i=0; i<lines.length; i++){
		line = lines[i];
		this.line(line[0],line[1], line[2],line[3]);
	}
}
FCG.prototype.setColor = function (index){ this.currentColor = index; }
FCG.prototype.fillPart = function (index){
	var parts = LEVEL[this.currentLevel].parts;
	this.ctx.fillStyle = DEFAULT_COLOR[this.currentColor];
	console.log('parts ' + index, parts[index]);
	this.ctx.fillRect(parts[index][0]+1, parts[index][1]+1, parts[index][2]-1,parts[index][3]-1);
	this.partsColor[index] = this.currentColor;
}
FCG.prototype.setLevelText = function (){ var lvl = getElmt('level'); lvl.innerText = this.currentLevel+1; }
FCG.prototype.initPartsColor = function(){
	var parts = LEVEL[this.currentLevel].parts
	this.partsColor = new Array();

	for (var i=0; i<parts.length; i++){
		this.partsColor[i] = 0;
	}
}
FCG.prototype.start = function (){
	var level = LEVEL[this.currentLevel];

	this.currentColor = 0;
	this.setLevelText();
	this.initPartsColor();
	this.drawLines(level.lines);
	for (var i=0; i<this.partsColor.length; i++){
		this.fillPart(i);
	}
}
FCG.prototype.next = function(){
	var max = LEVEL.length - 1;

	if (max == this.currentLevel){
		this.currentLevel = 0;

		this.win();
	} else if (max > this.currentLevel){
		this.currentLevel++;
		this.start();
	}
}
FCG.prototype.win = function(){
	alert('You are the winner');
	this.start();
}
FCG.prototype.validate = function(){
	var valid = true;
	var neighbours = LEVEL[this.currentLevel].neighbours

	for (var i=0; i<this.partsColor.length; i++){
		if (this.partsColor[i] == 0){
			valid = false;
			break;
		}
	}
	if (valid){
		for (var i=0; i<neighbours.length; i++){
			for (var j=0; j<neighbours[i].length; j++){
				if ((neighbours[i][j] == 1) && (this.partsColor[i] == this.partsColor[j])){
					valid = false;
					break;
				}
			}
			if (!valid) break;
		}
	}
	return valid;
}
FCG.prototype.calculatePosition = function(event){
	var x = event.x;
	var y = event.y;
	console.log('original click', x + ',' + y);
	console.log('offsetLeft', this.cdom.offsetLeft);
	console.log('offsetTop', this.cdom.offsetTop);
	x = x - this.cdom.offsetLeft + window.scrollX;
	y = y - this.cdom.offsetTop + window.scrollY;
	return [x,y];
}
FCG.prototype.getPartByPoint = function(position){
	var result = -1;
	var parts = LEVEL[this.currentLevel].parts;
	var part;

	for (var i=0; i<parts.length; i++){
		part = new Rect(parts[i][0], parts[i][1], parts[i][0] + parts[i][2], parts[i][1] + parts[i][3]);
		if (part.isInside(position[0],position[1])){
			result = i;
			break;
		}
	}
	return result;
}
function Rect(x1,y1, x2,y2){
	this.x1 = x1;
	this.y1 = y1;
	this.x2 = x2;
	this.y2 = y2;
}
Rect.prototype.isInside = function(x, y){
	return x >= this.x1 && x <= this.x2 && y >= this.y1 && y <= this.y2;
}
function startGame(){
	fcg = new FCG();
	fcg.start();
	var btn = getElmt('validate');
	var reset = getElmt('reset');
	var red = getElmt('red');
	var green = getElmt('green');
	var blue = getElmt('blue');
	var yellow = getElmt('yellow');
	btn.addEventListener("click", function(){
		valid = fcg.validate();
		console.log('valid', valid);
		if (valid){
			fcg.next();
		} else {
			alert("Invalid fill.\nAll parts must be filled with no default color.\nNeighbouring parts must be filled with different color.");
		}
	});
	red.addEventListener('click', function(){
		fcg.currentColor = 1;
	});
	green.addEventListener('click', function(){
		fcg.currentColor = 2;
	});
	blue.addEventListener('click', function(){
		fcg.currentColor = 3;
	});
	yellow.addEventListener('click', function(){
		fcg.currentColor = 4;
	});
	reset.addEventListener('click', function(){
		var input = confirm("Are you sure want to clear all parts at this level?");
		if (input == true){
			fcg.start();
		}
	});
	fcg.cdom.addEventListener("mousedown", function (event){
		var clickPos = fcg.calculatePosition(event);
		console.log('Calibrated position', clickPos[0] + ',' + clickPos[1]);
		var partNumber = fcg.getPartByPoint(clickPos);
		console.log('click in area', partNumber);
		fcg.fillPart(partNumber);
	}, false);
}<|endoftext|>


<commit_before>'use strict'
/* eslint-disable no-return-assign */
const qw = require('qw')
const Bluebird = require('bluebird')

let Site

class Fic {
  constructor (fetch) {
    this._id = null
    this.fetch = fetch
    this.title = null
    this.link = null
    this.altlinks = null
    this.updateFrom = null
    this.author = null
    this.authorUrl = null
    this.created = null
    this.modified = null
    this.publisher = null
    this.description = null
    this.notes = null
    this.cover = null
    this.chapterHeadings = null
    this.externals = null
    this.spoilers = null
    this.words = null
    this.tags = []
    this.fics = []
    this.chapters = new ChapterList()
    this.site = null
    this._includeTOC = null
    this._numberTOC = null
    this.fetchMeta = null
    this.scrapeMeta = null
  }

  get id () {
    if (this._id) return this._id
    const link = this.link || this.updateFrom
    if (link) return 'url:' + link
  }
  set id (value) {
    return this._id = value
  }

  get includeTOC () {
    return this._includeTOC === null ? true : this._includeTOC
  }

  set includeTOC (value) {
    this._includeTOC = value
  }

  get numberTOC () {
    return this._numberTOC === null ? true : this._numberTOC
  }

  set numberTOC (value) {
    this._numberTOC = value
  }

  get words () {
    return this.chapters.filter(ch => ch.type === 'chapter').reduce((acc, ch) => acc + ch.words, 0)
  }

  set words (val) {
    return
  }

  updateWith () {
    return this.updateFrom || this.link
  }

  chapterExists (link) {
    if (link == null) return false
    if (this.chapters.chapterExists(link, this)) return true
    if (this.fics.some(fic => fic.chapterExists(link))) return true
    return false
  }

  normalizeLink (link) {
    try {
      const site = Site.fromUrl(link)
      return site.normalizeLink(link)
    } catch (_) {
      return link
    }
  }

  addChapter (opts) {
    if (this.chapterExists(opts.link) || this.chapterExists(opts.fetchFrom)) return
    if (opts.spoilers === null) opts.spoilers = this.spoilers
    this.chapters.addChapter(opts)
  }

  importFromJSON (raw) {
    const props = qw`id link altlinks title author authorUrl created modified
     description notes tags publisher cover chapterHeadings words updateFrom
     includeTOC numberTOC fetchMeta scrapeMeta`

    for (let prop of props) {
      if (prop in raw) this[prop] = raw[prop]
    }
    this.externals = raw.externals != null ? raw.externals : true
    this.spoilers = raw.spoilers != null ? raw.spoilers : true
    for (let prop of Object.keys(raw)) {
      if (props.indexOf(prop) !== -1) continue
      if (prop !== 'chapters' && prop !== 'fics' && prop !== 'externals' && prop !== 'spoilers') {
        process.emit('warn', `Unknown property when importing fic: "${prop}"`)
      }
    }
    this.chapters.importFromJSON(this, raw)
    if (raw.fics) {
      for (let fic of raw.fics) {
        this.fics.push(SubFic.fromJSON(this, fic))
      }
    }
    try {
      this.site = Site.fromUrl(this.updateWith())
    } catch (ex) {
      process.emit('warn', ex)
    }
    return this
  }

  static fromUrl (fetch, link) {
    const fic = new this(fetch)
    fic.site = Site.fromUrl(link)
    fic.link = fic.site.link
    return fic.site.getFicMetadata(fetch, fic).then(thenMaybeFallback, elseMaybeFallback).then(() => fic)
    function elseMaybeFallback (err) {
      if (err && (!err.meta || err.meta.status !== 404)) throw err
      return thenMaybeFallback(err)
    }
    function thenMaybeFallback (err) {
      if (fic.chapters.length === 0 ) {
        fic.scrapeMeta = true
        if (fic.site.canScrape) {
          return fic.site.scrapeFicMetadata(fetch, fic).catch(scrapeErr => Bluebird.reject(err || scrapeErr))
        } else {
          if (!err) {
            err = new Error(`Could not fetch: ${link}`)
            err.code = 404
            err.url = link
          }
          return Bluebird.reject(err)
        }
      } else {
        fic.fetchMeta = true
      }
    }
  }

  static fromOnlyUrl (fetch, link) {
    const fic = new this(fetch)
    fic.site = Site.fromUrl(link)
    fic.link = fic.site.link
    return fic.site.getFicMetadata(fetch, fic).then(thenMaybeFallback, elseMaybeFallback).then(() => fic)
    function elseMaybeFallback (err) {
      if (err && (!err.meta || err.meta.status !== 404)) throw err
      return thenMaybeFallback(err)
    }
    function thenMaybeFallback (err) {
      if (fic.chapters.length === 0 ) {
        if (!err) {
          err = new Error(`Could not fetch: ${link}`)
          err.code = 404
          err.url = link
        }
        return Bluebird.reject(err)
      } else {
        fic.fetchMeta = true
      }
    }
  }

  static fromUrlAndScrape (fetch, link) {
    const fic = new this(fetch)
    fic.site = Site.fromUrl(link)
    fic.link = fic.site.link
    fic.fetchMeta = true
    fic.scrapeMeta = true
    return fic.site.getFicMetadata(fetch, fic).then(() => {
      if (fic.site.canScrape) {
        return fic.site.scrapeFicMetadata(fetch, fic).then(() => fic)
      } else {
        return fic
      }
    })
  }

  static scrapeFromUrl (fetch, link) {
    const fic = new this()
    fic.site = Site.fromUrl(link)
    fic.link = fic.site.link
    fic.scrapeMeta = true
    if (!fic.site.canScrape) {
      const err = new Error(`Site ${fic.site.publisherName || fic.site.publisher} does not support fetching via scraping for ${fic.title} @ ${fic.link}`)
      err.code = 'ENOSCRAPE'
      return Bluebird.reject(err)
    }
    return fic.site.scrapeFicMetadata(fetch, fic).then(() => fic)
  }

  static fromJSON (raw) {
    const fic = new this()
    return fic.importFromJSON(raw)
  }

  toJSON () {
    const result = {}
    for (let prop of qw`
         title _id link altlinks updateFrom author authorUrl created modified publisher cover
         description notes tags words fics chapters chapterHeadings _includeTOC _numberTOC fetchMeta scrapeMeta
       `) {
      if (this[prop] != null && (!Array.isArray(this[prop]) || this[prop].length)) result[prop.replace(/^_/,'')] = this[prop]
    }
    result.fics && result.fics.sort((a, b) => a.created > b.created ? 1 : a.created < b.created ? -1 : 0)
    if (!this.externals) result.externals = this.externals
    if (!this.spoilers) result.spoilers = this.spoilers
    return result
  }
}

class SubFic extends Fic {
  constructor (parentFic) {
    super()
    this.parent = parentFic
    delete this.fics
    for (let prop of qw`_title _created _modified _description _notes _link _author _authorUrl _tags _chapterHeadings`) {
      this[prop] = null
    }
  }
  chapterExists (link) {
    return this.chapters.chapterExists(link, this)
  }
  static fromJSON (parent, raw) {
    const fic = new this(parent)
    fic.importFromJSON(raw)
    return fic
  }
  get author () {
    return this._author || (this.chapters.length && this.chapters[0].author)|| this.parent.author
  }
  set author (value) {
    return this._author = value
  }
  get authorUrl () {
    return this._authorUrl || (this.chapters.length && this.chapters[0].authorurl)|| this.parent.authorUrl
  }
  set authorUrl (value) {
    return this._authorUrl = value
  }
  get publisher () {
    return this._publisher || this.parent.publisher
  }
  set publisher (value) {
    return this._publisher = value
  }
  get title () {
    return this._title || (this.chapters.length && this.chapters[0].name)
  }
  set title (value) {
    return this._title = value
  }
  get link () {
    return this._link || (this.chapters.length && this.chapters[0].link)
  }
  set link (value) {
    return this._link = value
  }
  get description () {
    return this._description || (this.chapters.length && this.chapters[0].description)
  }
  set description (value) {
    return this._description = value
  }
  get notes () {
    return this._notes || (this.chapters.length && this.chapters[0].notes)
  }
  set notes (value) {
    return this._notes = value
  }
  get created () {
    return this._created || (this.chapters.length && this.chapters[0].created)
  }
  set created (value) {
    return this._created = value
  }
  get modified () {
    const lastChapter = this.chapters.length && this.chapters[this.chapters.length-1]
    return this._modified || (lastChapter && (lastChapter.modified || lastChapter.created))
  }
  set modified (value) {
    return this._modified = value
  }
  get chapterHeadings () {
    return this._chapterHeadings || this.parent.chapterHeadings
  }
  set chapterHeadings (value) {
    return this._chapterHeadings = value
  }
  get externals () {
    return this._externals || this.parent.externals
  }
  set externals (value) {
    return this._externals = value
  }
  get spoilers () {
    return this._spoilers || this.parent.spoilers
  }
  set spoilers (value) {
    return this._spoilers = value
  }
  get tags () {
    if (!this._tags) return Object.assign([], this.parent.tags)
    return this._tags
  }
  set tags (value) {
    if (value.length === 0) value = null
    return this._tags = value
  }
  toJSON () {
    const result = {}
    for (let prop of qw`
         _title _id _link altlinks _author _authorUrl _created _modified _publisher
         _description _notes _tags chapters _chapterHeadings words _includeTOC _numberTOC
         `) {
      const assignTo = prop[0] === '_' ? prop.slice(1) : prop
      if (this[prop] && (this[prop].length == null || this[prop].length)) result[assignTo] = this[prop]
    }
    return result
  }
}

class ChapterList extends Array {
  chapterExists (link, fic) {
    if (link == null) {
      return
    } else if (fic) {
      const normalizedLink = fic.normalizeLink(link)
      return this.some(chap => fic.normalizeLink(chap.link) === normalizedLink || chap.fetchFrom === normalizedLink)
    } else {
      return this.some(chap => chap.link === link || chap.fetchFrom === link)
    }
  }
  addChapter (opts) {
    if (this.chapterExists(opts.fetchFrom) || this.chapterExists(opts.link)) return
    let name = opts.name
    let ctr = 0
    while (this.some(chap => chap.name === name)) {
      name = opts.name + ' (' + ++ctr + ')'
    }
    if (opts.created && (!this.created || opts.created < this.created)) this.created = opts.created
    this.push(new Chapter(Object.assign({}, opts, {name, order: this.length})))
    this.sort()
  }
  sort () {
    const types = {}
    types['chapter'] = 0
    types['Sidestory'] = 50
    types['Media'] = 75
    types['Informational'] = 90
    types['Apocrypha'] = 100
    types['Staff Post'] = 9999
    Array.prototype.sort.call(this, (a, b) => {
      return (types[a.type] - types[b.type]) || a.order - b.order
    })
  }
  importFromJSON (fic, raw) {
    if (raw.fics && !raw.chapters) return
    if (!raw.chapters) {
      const err = new Error('Fic "' + raw.title + '" is missing any chapters.')
      err.code = 'ENOCHAPTERS'
      throw err
    }
    for (let chapter of raw.chapters) {
      if (chapter.spoilers == null) chapter.spoilers = fic.spoilers
      this.push(Chapter.fromJSON(this.length, chapter))
    }
    this.sort()
  }
}

class Chapter {
  constructor (opts) {
    this.order = opts.order
    this.name = opts.name
    this.link = opts.link
    if (opts.type) {
      this.type = opts.type
    } else if (/^Omake:/.test(this.name)) {
      this.type = 'Sidestory'
    } else if (/^Appendix:/.test(this.name)) {
      this.type = 'Apocrypha'
    } else if (/^Art:/.test(this.name)) {
      this.type = 'Media'
    } else {
      this.type = 'chapter'
    }
    this.description = opts.description
    this.notes = opts.notes
    this.fetchFrom = opts.fetchFrom
    this.created = opts.created
    this.modified = opts.modified
    this.author = opts.author
    this.authorUrl = opts.authorUrl
    this.tags = opts.tags
    this.externals = opts.externals != null ? opts.externals : true
    this.spoilers = opts.spoilers != null ? opts.spoilers : true
    this.headings = opts.headings
    this.words = opts.words || 0
  }
  toJSON () {
    return {
      name: this.name,
      type: this.type !== 'chapter' ? this.type : undefined,
      description: this.description,
      notes: this.notes,
      link: this.link,
      fetchFrom: this.fetchFrom,
      author: this.author,
      authorUrl: this.authorUrl,
      created: this.created === 'Invalid Date' ? null : this.created,
      modified: this.modified === 'Invalid Date' ? null : this.modified,
      tags: this.tags,
      externals: this.externals !== true ? this.externals : null,
      spoilers: this.spoilers !== true ? this.spoilers: null,
      headings: this.headings,
      words: this.words
    }
  }
  static fromJSON (order, opts) {
    return new Chapter(Object.assign({order}, opts))
  }
  fetchWith () {
    return this.fetchFrom || this.link
  }
  getContent (fetch) {
    const site = Site.fromUrl(this.fetchWith())
    return site.getChapter(fetch, this)
  }
  static getContent (fetch, href) {
    return (new this({link: href})).getContent(fetch)
  }
}

module.exports = Fic
module.exports.SubFic = SubFic
module.exports.Chapter = Chapter

// defer 'cause `class` definitions don't hoist
Site = use('site')
<commit_msg>fic: Don't export an empty tags array<commit_after>'use strict'
/* eslint-disable no-return-assign */
const qw = require('qw')
const Bluebird = require('bluebird')

let Site

class Fic {
  constructor (fetch) {
    this._id = null
    this.fetch = fetch
    this.title = null
    this.link = null
    this.altlinks = null
    this.updateFrom = null
    this.author = null
    this.authorUrl = null
    this.created = null
    this.modified = null
    this.publisher = null
    this.description = null
    this.notes = null
    this.cover = null
    this.chapterHeadings = null
    this.externals = null
    this.spoilers = null
    this.words = null
    this.tags = []
    this.fics = []
    this.chapters = new ChapterList()
    this.site = null
    this._includeTOC = null
    this._numberTOC = null
    this.fetchMeta = null
    this.scrapeMeta = null
  }

  get id () {
    if (this._id) return this._id
    const link = this.link || this.updateFrom
    if (link) return 'url:' + link
  }
  set id (value) {
    return this._id = value
  }

  get includeTOC () {
    return this._includeTOC === null ? true : this._includeTOC
  }

  set includeTOC (value) {
    this._includeTOC = value
  }

  get numberTOC () {
    return this._numberTOC === null ? true : this._numberTOC
  }

  set numberTOC (value) {
    this._numberTOC = value
  }

  get words () {
    return this.chapters.filter(ch => ch.type === 'chapter').reduce((acc, ch) => acc + ch.words, 0)
  }

  set words (val) {
    return
  }

  updateWith () {
    return this.updateFrom || this.link
  }

  chapterExists (link) {
    if (link == null) return false
    if (this.chapters.chapterExists(link, this)) return true
    if (this.fics.some(fic => fic.chapterExists(link))) return true
    return false
  }

  normalizeLink (link) {
    try {
      const site = Site.fromUrl(link)
      return site.normalizeLink(link)
    } catch (_) {
      return link
    }
  }

  addChapter (opts) {
    if (this.chapterExists(opts.link) || this.chapterExists(opts.fetchFrom)) return
    if (opts.spoilers === null) opts.spoilers = this.spoilers
    this.chapters.addChapter(opts)
  }

  importFromJSON (raw) {
    const props = qw`id link altlinks title author authorUrl created modified
     description notes tags publisher cover chapterHeadings words updateFrom
     includeTOC numberTOC fetchMeta scrapeMeta`

    for (let prop of props) {
      if (prop in raw) this[prop] = raw[prop]
    }
    this.externals = raw.externals != null ? raw.externals : true
    this.spoilers = raw.spoilers != null ? raw.spoilers : true
    for (let prop of Object.keys(raw)) {
      if (props.indexOf(prop) !== -1) continue
      if (prop !== 'chapters' && prop !== 'fics' && prop !== 'externals' && prop !== 'spoilers') {
        process.emit('warn', `Unknown property when importing fic: "${prop}"`)
      }
    }
    this.chapters.importFromJSON(this, raw)
    if (raw.fics) {
      for (let fic of raw.fics) {
        this.fics.push(SubFic.fromJSON(this, fic))
      }
    }
    try {
      this.site = Site.fromUrl(this.updateWith())
    } catch (ex) {
      process.emit('warn', ex)
    }
    return this
  }

  static fromUrl (fetch, link) {
    const fic = new this(fetch)
    fic.site = Site.fromUrl(link)
    fic.link = fic.site.link
    return fic.site.getFicMetadata(fetch, fic).then(thenMaybeFallback, elseMaybeFallback).then(() => fic)
    function elseMaybeFallback (err) {
      if (err && (!err.meta || err.meta.status !== 404)) throw err
      return thenMaybeFallback(err)
    }
    function thenMaybeFallback (err) {
      if (fic.chapters.length === 0 ) {
        fic.scrapeMeta = true
        if (fic.site.canScrape) {
          return fic.site.scrapeFicMetadata(fetch, fic).catch(scrapeErr => Bluebird.reject(err || scrapeErr))
        } else {
          if (!err) {
            err = new Error(`Could not fetch: ${link}`)
            err.code = 404
            err.url = link
          }
          return Bluebird.reject(err)
        }
      } else {
        fic.fetchMeta = true
      }
    }
  }

  static fromOnlyUrl (fetch, link) {
    const fic = new this(fetch)
    fic.site = Site.fromUrl(link)
    fic.link = fic.site.link
    return fic.site.getFicMetadata(fetch, fic).then(thenMaybeFallback, elseMaybeFallback).then(() => fic)
    function elseMaybeFallback (err) {
      if (err && (!err.meta || err.meta.status !== 404)) throw err
      return thenMaybeFallback(err)
    }
    function thenMaybeFallback (err) {
      if (fic.chapters.length === 0 ) {
        if (!err) {
          err = new Error(`Could not fetch: ${link}`)
          err.code = 404
          err.url = link
        }
        return Bluebird.reject(err)
      } else {
        fic.fetchMeta = true
      }
    }
  }

  static fromUrlAndScrape (fetch, link) {
    const fic = new this(fetch)
    fic.site = Site.fromUrl(link)
    fic.link = fic.site.link
    fic.fetchMeta = true
    fic.scrapeMeta = true
    return fic.site.getFicMetadata(fetch, fic).then(() => {
      if (fic.site.canScrape) {
        return fic.site.scrapeFicMetadata(fetch, fic).then(() => fic)
      } else {
        return fic
      }
    })
  }

  static scrapeFromUrl (fetch, link) {
    const fic = new this()
    fic.site = Site.fromUrl(link)
    fic.link = fic.site.link
    fic.scrapeMeta = true
    if (!fic.site.canScrape) {
      const err = new Error(`Site ${fic.site.publisherName || fic.site.publisher} does not support fetching via scraping for ${fic.title} @ ${fic.link}`)
      err.code = 'ENOSCRAPE'
      return Bluebird.reject(err)
    }
    return fic.site.scrapeFicMetadata(fetch, fic).then(() => fic)
  }

  static fromJSON (raw) {
    const fic = new this()
    return fic.importFromJSON(raw)
  }

  toJSON () {
    const result = {}
    for (let prop of qw`
         title _id link altlinks updateFrom author authorUrl created modified publisher cover
         description notes tags words fics chapters chapterHeadings _includeTOC _numberTOC fetchMeta scrapeMeta
       `) {
      if (this[prop] != null && (!Array.isArray(this[prop]) || this[prop].length)) result[prop.replace(/^_/,'')] = this[prop]
    }
    result.fics && result.fics.sort((a, b) => a.created > b.created ? 1 : a.created < b.created ? -1 : 0)
    if (!this.externals) result.externals = this.externals
    if (!this.spoilers) result.spoilers = this.spoilers
    return result
  }
}

class SubFic extends Fic {
  constructor (parentFic) {
    super()
    this.parent = parentFic
    delete this.fics
    for (let prop of qw`_title _created _modified _description _notes _link _author _authorUrl _tags _chapterHeadings`) {
      this[prop] = null
    }
  }
  chapterExists (link) {
    return this.chapters.chapterExists(link, this)
  }
  static fromJSON (parent, raw) {
    const fic = new this(parent)
    fic.importFromJSON(raw)
    return fic
  }
  get author () {
    return this._author || (this.chapters.length && this.chapters[0].author)|| this.parent.author
  }
  set author (value) {
    return this._author = value
  }
  get authorUrl () {
    return this._authorUrl || (this.chapters.length && this.chapters[0].authorurl)|| this.parent.authorUrl
  }
  set authorUrl (value) {
    return this._authorUrl = value
  }
  get publisher () {
    return this._publisher || this.parent.publisher
  }
  set publisher (value) {
    return this._publisher = value
  }
  get title () {
    return this._title || (this.chapters.length && this.chapters[0].name)
  }
  set title (value) {
    return this._title = value
  }
  get link () {
    return this._link || (this.chapters.length && this.chapters[0].link)
  }
  set link (value) {
    return this._link = value
  }
  get description () {
    return this._description || (this.chapters.length && this.chapters[0].description)
  }
  set description (value) {
    return this._description = value
  }
  get notes () {
    return this._notes || (this.chapters.length && this.chapters[0].notes)
  }
  set notes (value) {
    return this._notes = value
  }
  get created () {
    return this._created || (this.chapters.length && this.chapters[0].created)
  }
  set created (value) {
    return this._created = value
  }
  get modified () {
    const lastChapter = this.chapters.length && this.chapters[this.chapters.length-1]
    return this._modified || (lastChapter && (lastChapter.modified || lastChapter.created))
  }
  set modified (value) {
    return this._modified = value
  }
  get chapterHeadings () {
    return this._chapterHeadings || this.parent.chapterHeadings
  }
  set chapterHeadings (value) {
    return this._chapterHeadings = value
  }
  get externals () {
    return this._externals || this.parent.externals
  }
  set externals (value) {
    return this._externals = value
  }
  get spoilers () {
    return this._spoilers || this.parent.spoilers
  }
  set spoilers (value) {
    return this._spoilers = value
  }
  get tags () {
    if (!this._tags) return Object.assign([], this.parent.tags)
    return this._tags
  }
  set tags (value) {
    if (value.length === 0) value = null
    return this._tags = value
  }
  toJSON () {
    const result = {}
    for (let prop of qw`
         _title _id _link altlinks _author _authorUrl _created _modified _publisher
         _description _notes _tags chapters _chapterHeadings words _includeTOC _numberTOC
         `) {
      const assignTo = prop[0] === '_' ? prop.slice(1) : prop
      if (this[prop] && (this[prop].length == null || this[prop].length)) result[assignTo] = this[prop]
    }
    return result
  }
}

class ChapterList extends Array {
  chapterExists (link, fic) {
    if (link == null) {
      return
    } else if (fic) {
      const normalizedLink = fic.normalizeLink(link)
      return this.some(chap => fic.normalizeLink(chap.link) === normalizedLink || chap.fetchFrom === normalizedLink)
    } else {
      return this.some(chap => chap.link === link || chap.fetchFrom === link)
    }
  }
  addChapter (opts) {
    if (this.chapterExists(opts.fetchFrom) || this.chapterExists(opts.link)) return
    let name = opts.name
    let ctr = 0
    while (this.some(chap => chap.name === name)) {
      name = opts.name + ' (' + ++ctr + ')'
    }
    if (opts.created && (!this.created || opts.created < this.created)) this.created = opts.created
    this.push(new Chapter(Object.assign({}, opts, {name, order: this.length})))
    this.sort()
  }
  sort () {
    const types = {}
    types['chapter'] = 0
    types['Sidestory'] = 50
    types['Media'] = 75
    types['Informational'] = 90
    types['Apocrypha'] = 100
    types['Staff Post'] = 9999
    Array.prototype.sort.call(this, (a, b) => {
      return (types[a.type] - types[b.type]) || a.order - b.order
    })
  }
  importFromJSON (fic, raw) {
    if (raw.fics && !raw.chapters) return
    if (!raw.chapters) {
      const err = new Error('Fic "' + raw.title + '" is missing any chapters.')
      err.code = 'ENOCHAPTERS'
      throw err
    }
    for (let chapter of raw.chapters) {
      if (chapter.spoilers == null) chapter.spoilers = fic.spoilers
      this.push(Chapter.fromJSON(this.length, chapter))
    }
    this.sort()
  }
}

class Chapter {
  constructor (opts) {
    this.order = opts.order
    this.name = opts.name
    this.link = opts.link
    if (opts.type) {
      this.type = opts.type
    } else if (/^Omake:/.test(this.name)) {
      this.type = 'Sidestory'
    } else if (/^Appendix:/.test(this.name)) {
      this.type = 'Apocrypha'
    } else if (/^Art:/.test(this.name)) {
      this.type = 'Media'
    } else {
      this.type = 'chapter'
    }
    this.description = opts.description
    this.notes = opts.notes
    this.fetchFrom = opts.fetchFrom
    this.created = opts.created
    this.modified = opts.modified
    this.author = opts.author
    this.authorUrl = opts.authorUrl
    this.tags = opts.tags
    this.externals = opts.externals != null ? opts.externals : true
    this.spoilers = opts.spoilers != null ? opts.spoilers : true
    this.headings = opts.headings
    this.words = opts.words || 0
  }
  toJSON () {
    return {
      name: this.name,
      type: this.type !== 'chapter' ? this.type : undefined,
      description: this.description,
      notes: this.notes,
      link: this.link,
      fetchFrom: this.fetchFrom,
      author: this.author,
      authorUrl: this.authorUrl,
      created: this.created === 'Invalid Date' ? null : this.created,
      modified: this.modified === 'Invalid Date' ? null : this.modified,
      tags: this.tags && this.tags.length > 0 ? this.tags : null,
      externals: this.externals !== true ? this.externals : null,
      spoilers: this.spoilers !== true ? this.spoilers: null,
      headings: this.headings,
      words: this.words
    }
  }
  static fromJSON (order, opts) {
    return new Chapter(Object.assign({order}, opts))
  }
  fetchWith () {
    return this.fetchFrom || this.link
  }
  getContent (fetch) {
    const site = Site.fromUrl(this.fetchWith())
    return site.getChapter(fetch, this)
  }
  static getContent (fetch, href) {
    return (new this({link: href})).getContent(fetch)
  }
}

module.exports = Fic
module.exports.SubFic = SubFic
module.exports.Chapter = Chapter

// defer 'cause `class` definitions don't hoist
Site = use('site')
<|endoftext|>