<commit_before>/**
 * The main class of the API Browser.
 *
 * #asset(api/*)
 */
core.Class('api.Browser', {

	construct: function() {

		this.__headElem = document.createElement("div");
		this.__headElem.id = "head";
		this.__headElem.innerHTML = "API Documentation";

		this.__treeElem = document.createElement("div");
		this.__treeElem.id = "tree";

		this.__contentElem = document.createElement("div");
		this.__contentElem.id = "content";

		document.body.appendChild(this.__headElem);
		document.body.appendChild(this.__treeElem);
		document.body.appendChild(this.__contentElem);

		// Load initial data
		core.io.Queue.load([
			core.io.Asset.toUri("api/icon/stylesheet.css"),
			core.io.Asset.toUri("api/reset.css"),
			core.io.Asset.toUri("api/style.css"),
			core.io.Asset.toUri("api/syntax.css"),

			"tmpl/main.js",
			"tmpl/entry.js",
			"tmpl/type.js",
			"tmpl/params.js",
			"tmpl/info.js",
			"tmpl/origin.js",
			"tmpl/tags.js",
			"tmpl/error.js",

			"data/meta-index.js",
			"data/meta-search.js"
		], this.__onLoad, this);

		this.__tmpl = {};
		this.__cache = {};

		// This is mostly a cache for our callback / JSONP environment
		this.__current = {
			type: '',
			file: '',
			item: '',
			html: '',
			hash: ''
		};

	},

	members: {

		__onLoad : function() {

			var that = this;

			document.body.addEventListener('click', function(event) {

				var element = event.target;

				if (element) {

					if (element.tagName === 'H3') {
						// parentNode = LI
						element = element.parentNode;
					}

					if (element.tagName === 'LI') {

						if (element.id) {

							var type, item;
							if (element.id.match(/-/)) {
								type = element.id.split(/-/)[0];
								item = element.id.split(/-/)[1];
							} else {
								item = element.id;
							}

							that.open(that.getHash(type, null, item));

						}

						event.preventDefault();

					} else if (element.tagName === 'A') {

						if (element.getAttribute('href')) {

							var href = element.getAttribute('href');
							if (href.charAt(0) === '#') {
								that.open(href.slice(1));
							}

						}

						event.preventDefault();

					}

				}

			}, true);

			window.onhashchange = function() {

				var hash = location.hash.slice(1);
				if (hash !== that.__current.hash) {
					that.open(hash);
				}

			};

			// Open initial hash
			this.open(location.hash.slice(1));

		},

		callback: function(data, id) {

			if (id.endsWith(".mustache")) {

				var templateName = id.substring(0, id.indexOf(".mustache"));
				this.__tmpl[templateName] = core.template.Compiler.compile(data.template, true);

			} else if (id == "meta-index") {

				this.__treeElem.innerHTML = "
" + this.__treeWalker(data, "") + "
";

			} else if (id == "meta-search") {

				this.__search = data;

			} else {

				console.debug("Loaded File: " + id);

				this.__cache[id] = this.__tmpl.main.render(data, this.__tmpl);

				if (this.__current.file === id) {
					var hash = this.getHash(this.__current.type, this.__current.file, this.__current.item);
					this.open(hash);
				}

			}

		},

		__treeWalker: function(node, base) {

			var html = '';

			var filter = function(value) {
				return value.charAt(0) != "$";
			};

			var comparator = function(a, b) {
				if (node[a].$type != node[b].$type)
				{
					if (node[a].$type == "Package") {
						return -1;
					} else if (node[b].$type == "Package") {
						return 1;
					}
				}

				return a == b ? 0 : a > b ? 1 : -1;
			};

			var keys = Object.keys(node).filter(filter).sort(comparator);

			for (var i=0, l=keys.length; i<l; i++) {

				var key = keys[i];
				var entry = node[key];
				var name = base ? base + "." + key : key;

				// this will let showContent know that the file / class exists
				if (this.__cache[name] === undefined) {
					this.__cache[name] = null;
				}

				if (entry.$type == "Package") {

					html += '
' + key + '
' + this.__treeWalker(entry, name) + '

';

				} else {

					html += '
' + key + '
';

				}

			}

			return html;

		},

		getHash: function(type, file, item) {

			type = type || null;
			file = file || null;
			item = item || null;

			var hash = '';

			// MewTwo uses confusion! ZackBoomBang!
			if (item === null || type === null) {
				type = '';
			}

			if (type) {
				hash += type + ':';
			}

			if (file) {
				hash += file;
			} else {
				hash += this.__current.file || '';
			}

			if (item) {
				hash += '~' + item;
			}

			return hash;

		},

		getHashData: function(hash) {

			var regex = new RegExp("((static|member|property|event)\:)?([A-Za-z0-9_\.]+)?(\~([A-Za-z0-9_]+))");
			var tmp = hash.split(regex);

			var data = {
				type: null,
				file: null,
				item: null
			};

			// fastest access
			if (tmp.length === 1) {

				if (tmp[0]) {
					data.file = tmp[0];
				}

				return data;
			}

			// ignore empty strings

			if (tmp[2]) {
				data.type = tmp[2];
			}

			if (tmp[3]) {
				data.file = tmp[3];
			}

			if (tmp[5]) {
				data.item = tmp[5];
			}

			return data;

		},

		open: function(hash) {

			if (hash.charAt(0) === '!') {
				hash = hash.slice(1);
			}

			var data = this.getHashData(hash);
			console.log('OPEN', data);

			this.__showTree(data);
			this.__showContent(data);

			if (
				this.__current.type !== data.type
				|| this.__current.file !== data.file
				|| this.__current.item !== data.item
			) {

				// Don't overwrite this.__current.html
				// or this.__current.file with null!
				this.__current.type = data.type;
				this.__current.file = data.file || this.__current.file;
				this.__current.item = data.item;

				this.__current.hash = this.getHash(this.__current.type, this.__current.file, this.__current.item);
				location.hash = this.__current.hash;

			}

		},

		__showTree: function(data) {

			if (!data.file) {
				return;
			}

			var segments = data.file.split('.');
			var current = '';

			// Don't close opened items, just un-highlight them
			var oldElements = document.querySelectorAll("#tree .active");
			for (var o = 0; o < oldElements.length; o++) {
				core.bom.ClassName.remove(oldElements[o], 'active');
			}

			for (var s = 0, l = segments.length; s < l; s++) {

				current += segments[s];

				var element = document.querySelector("#tree a[href='#" + current + "']");
				if (element != null) {

					core.bom.ClassName.add(element.parentNode, 'open');

					// Highlight the deepest (= active) item
					if (s === segments.length - 1) {
						core.bom.ClassName.add(element.parentNode, 'active');
					}

				}

				current += '.';

			}

		},

		__showContent: function(data) {

			if (!data.file) {
				return;
			}

			var content = document.getElementById('content'),
				cacheEntry = this.__cache[data.file];

			if (cacheEntry === null) {

				core.io.Script.load('data/' + data.file + '.js', function(uri, error) {

					if (error === true) {

						content.innerHTML = this.__tmpl.error.render({
							name: '404 - Not Found',
							description: 'The selected File "' + data.file + '" was not found.'
						}, this.__tmpl);

						this.__current.html = data.file;

					}

				}, this);

				return;

			} else if (cacheEntry != null && this.__current.html !== data.file) {

				content.innerHTML = cacheEntry;
				this.__current.html = data.file;

			} else if (cacheEntry === undefined) {

				content.innerHTML = this.__tmpl.error.render({
					name: '404 - Not Found',
					description: 'The selected File "' + data.file + '" was not found.'
				}, this.__tmpl);

				this.__current.html = data.file;

				return;

			}

			if (data.item) {

				var element;
				if (data.type !== null) {
					element = document.getElementById(data.type + '-' + data.item);
				}

				// Not found? Search for all types (first found wins)
				if (!element || !data.type) {

					var types = [
						'static',
						'member',
						'property',
						'event'
					];

					for (var t = 0, l = types.length; t < l; t++) {

						data.type = types[t];
						element = document.getElementById(data.type + '-' + data.item);

						// First found wins
						if (element) break;

					}

				}

				var oldElements = document.querySelectorAll('#content li.open');
				for (var e = 0; e < oldElements.length; e++) {
					core.bom.ClassName.remove(oldElements[e], 'active');
				}

				if (element) {
					core.bom.ClassName.add(element, 'open');
					core.bom.ClassName.add(element, 'active');
					core.bom.ScrollInto.scrollY(element);
				}

			}

		}

	}

});

<commit_msg>Fine tweaked scrollIntoView<commit_after>/**
 * The main class of the API Browser.
 *
 * #asset(api/*)
 */
core.Class('api.Browser', {

	construct: function() {

		this.__headElem = document.createElement("div");
		this.__headElem.id = "head";
		this.__headElem.innerHTML = "API Documentation";

		this.__treeElem = document.createElement("div");
		this.__treeElem.id = "tree";

		this.__contentElem = document.createElement("div");
		this.__contentElem.id = "content";

		document.body.appendChild(this.__headElem);
		document.body.appendChild(this.__treeElem);
		document.body.appendChild(this.__contentElem);

		// Load initial data
		core.io.Queue.load([
			core.io.Asset.toUri("api/icon/stylesheet.css"),
			core.io.Asset.toUri("api/reset.css"),
			core.io.Asset.toUri("api/style.css"),
			core.io.Asset.toUri("api/syntax.css"),

			"tmpl/main.js",
			"tmpl/entry.js",
			"tmpl/type.js",
			"tmpl/params.js",
			"tmpl/info.js",
			"tmpl/origin.js",
			"tmpl/tags.js",
			"tmpl/error.js",

			"data/meta-index.js",
			"data/meta-search.js"
		], this.__onLoad, this);

		this.__tmpl = {};
		this.__cache = {};

		// This is mostly a cache for our callback / JSONP environment
		this.__current = {
			type: '',
			file: '',
			item: '',
			html: '',
			hash: ''
		};

	},

	members: {

		__onLoad : function() {

			var that = this;

			document.body.addEventListener('click', function(event) {

				var element = event.target;

				if (element) {

					if (element.tagName === 'H3') {
						// parentNode = LI
						element = element.parentNode;
					}

					if (element.tagName === 'LI') {

						if (element.id) {

							var type, item;
							if (element.id.match(/-/)) {
								type = element.id.split(/-/)[0];
								item = element.id.split(/-/)[1];
							} else {
								item = element.id;
							}

							that.open(that.getHash(type, null, item));

						}

						event.preventDefault();

					} else if (element.tagName === 'A') {

						if (element.getAttribute('href')) {

							var href = element.getAttribute('href');
							if (href.charAt(0) === '#') {
								that.open(href.slice(1));
							}

						}

						event.preventDefault();

					}

				}

			}, true);

			window.onhashchange = function() {

				var hash = location.hash.slice(1);
				if (hash !== that.__current.hash) {
					that.open(hash);
				}

			};

			// Open initial hash
			this.open(location.hash.slice(1));

		},

		callback: function(data, id) {

			if (id.endsWith(".mustache")) {

				var templateName = id.substring(0, id.indexOf(".mustache"));
				this.__tmpl[templateName] = core.template.Compiler.compile(data.template, true);

			} else if (id == "meta-index") {

				this.__treeElem.innerHTML = "
" + this.__treeWalker(data, "") + "
";

			} else if (id == "meta-search") {

				this.__search = data;

			} else {

				console.debug("Loaded File: " + id);

				this.__cache[id] = this.__tmpl.main.render(data, this.__tmpl);

				if (this.__current.file === id) {
					var hash = this.getHash(this.__current.type, this.__current.file, this.__current.item);
					this.open(hash);
				}

			}

		},

		__treeWalker: function(node, base) {

			var html = '';

			var filter = function(value) {
				return value.charAt(0) != "$";
			};

			var comparator = function(a, b) {
				if (node[a].$type != node[b].$type)
				{
					if (node[a].$type == "Package") {
						return -1;
					} else if (node[b].$type == "Package") {
						return 1;
					}
				}

				return a == b ? 0 : a > b ? 1 : -1;
			};

			var keys = Object.keys(node).filter(filter).sort(comparator);

			for (var i=0, l=keys.length; i<l; i++) {

				var key = keys[i];
				var entry = node[key];
				var name = base ? base + "." + key : key;

				// this will let showContent know that the file / class exists
				if (this.__cache[name] === undefined) {
					this.__cache[name] = null;
				}

				if (entry.$type == "Package") {

					html += '
' + key + '
' + this.__treeWalker(entry, name) + '

';

				} else {

					html += '
' + key + '
';

				}

			}

			return html;

		},

		getHash: function(type, file, item) {

			type = type || null;
			file = file || null;
			item = item || null;

			var hash = '';

			// MewTwo uses confusion! ZackBoomBang!
			if (item === null || type === null) {
				type = '';
			}

			if (type) {
				hash += type + ':';
			}

			if (file) {
				hash += file;
			} else {
				hash += this.__current.file || '';
			}

			if (item) {
				hash += '~' + item;
			}

			return hash;

		},

		getHashData: function(hash) {

			var regex = new RegExp("((static|member|property|event)\:)?([A-Za-z0-9_\.]+)?(\~([A-Za-z0-9_]+))");
			var tmp = hash.split(regex);

			var data = {
				type: null,
				file: null,
				item: null
			};

			// fastest access
			if (tmp.length === 1) {

				if (tmp[0]) {
					data.file = tmp[0];
				}

				return data;
			}

			// ignore empty strings

			if (tmp[2]) {
				data.type = tmp[2];
			}

			if (tmp[3]) {
				data.file = tmp[3];
			}

			if (tmp[5]) {
				data.item = tmp[5];
			}

			return data;

		},

		open: function(hash) {

			if (hash.charAt(0) === '!') {
				hash = hash.slice(1);
			}

			var data = this.getHashData(hash);
			console.log('OPEN', data);

			this.__showTree(data);
			this.__showContent(data);

			if (
				this.__current.type !== data.type
				|| this.__current.file !== data.file
				|| this.__current.item !== data.item
			) {

				// Don't overwrite this.__current.html
				// or this.__current.file with null!
				this.__current.type = data.type;
				this.__current.file = data.file || this.__current.file;
				this.__current.item = data.item;

				this.__current.hash = this.getHash(this.__current.type, this.__current.file, this.__current.item);
				location.hash = this.__current.hash;

			}

		},

		__showTree: function(data) {

			if (!data.file) {
				return;
			}

			var segments = data.file.split('.');
			var current = '';

			// Don't close opened items, just un-highlight them
			var oldElements = document.querySelectorAll("#tree .active");
			for (var o = 0; o < oldElements.length; o++) {
				core.bom.ClassName.remove(oldElements[o], 'active');
			}

			for (var s = 0, l = segments.length; s < l; s++) {

				current += segments[s];

				var element = document.querySelector("#tree a[href='#" + current + "']");
				if (element != null) {

					core.bom.ClassName.add(element.parentNode, 'open');

					// Highlight the deepest (= active) item
					if (s === segments.length - 1) {
						core.bom.ClassName.add(element.parentNode, 'active');
					}

				}

				current += '.';

			}

		},

		__showContent: function(data) {

			if (!data.file) {
				return;
			}

			var content = document.getElementById('content'),
				cacheEntry = this.__cache[data.file];

			var fileChanged = this.__current.html !== data.file;

			if (cacheEntry === null) {

				core.io.Script.load('data/' + data.file + '.js', function(uri, error) {

					if (error === true) {

						content.innerHTML = this.__tmpl.error.render({
							name: '404 - Not Found',
							description: 'The selected File "' + data.file + '" was not found.'
						}, this.__tmpl);

						this.__current.html = data.file;

					}

				}, this);

				return;

			} else if (cacheEntry != null && fileChanged) {

				content.innerHTML = cacheEntry;
				this.__current.html = data.file;

			} else if (cacheEntry === undefined) {

				content.innerHTML = this.__tmpl.error.render({
					name: '404 - Not Found',
					description: 'The selected File "' + data.file + '" was not found.'
				}, this.__tmpl);

				this.__current.html = data.file;

				return;

			}

			if (data.item) {

				var element;
				if (data.type !== null) {
					element = document.getElementById(data.type + '-' + data.item);
				}

				// Not found? Search for all types (first found wins)
				if (!element || !data.type) {

					var types = [
						'static',
						'member',
						'property',
						'event'
					];

					for (var t = 0, l = types.length; t < l; t++) {

						data.type = types[t];
						element = document.getElementById(data.type + '-' + data.item);

						// First found wins
						if (element) break;

					}

				}

				var oldElements = document.querySelectorAll('#content li.open');
				for (var e = 0; e < oldElements.length; e++) {
					core.bom.ClassName.remove(oldElements[e], 'active');
				}

				if (element) {
					core.bom.ClassName.add(element, 'open');
					core.bom.ClassName.add(element, 'active');

					core.bom.ScrollInto.scrollY(element, fileChanged ? "top" : null);
				}

			}

		}

	}

});

<|endoftext|>


<commit_before>var fs = require('fs');
var child_process = require('child_process')
var max_sleep = 300

if ( process.argv[ 2 ] && process.argv[ 3 ] ) {
  var inFile = process.argv[ 2 ]
  var outFile = process.argv[ 3 ]
  if (process.argv [ 4 ]) max_sleep = process.argv [ 4 ]
  console.info("Writing from %s to %s, with up to %s sec<commit_msg>Update character<commit_after>var fs = require('fs');
var child_process = require('child_process')
var max_sleep = 300

if ( process.argv[ 2 ] && process.argv[ 3 ] ) {
  var inFile = process.argv[ 2 ]
  var outFile = process.argv[ 3 ]
  if (process.argv [ 4 ]) max_sleep = process.argv [ 4 ]
  console.info("Writing from %s to %s, with up to %s seco<|endoftext|>


<commit_before>var fs = require('fs');
var datehelper = require('../modules/datehelper');
var exec = require('child_process').exec;

module.exports = function(app, layout) {
	app.get('/changelog/?', function(req, res, next) {
		try {
			getGitLog(function (err, data) {
				layout.render(req, res, {
						pageTemplate: 'page_changelog',
						commits: data
					});
			});
		} catch (e) {
			layout.render(req, res, dialog({
				message: '¡Ups! Me he hecho caca',
				action: layout.getReturnUrl(req, true),
				error: true
			}).asGlobal());
			console.log(e, e.stack);
		}
	});
};

var getGitLog = function(cb) {
	var each = function(array, fn) {
		for(var i = 0, ilen = array.length; i < ilen; i++) {
			fn(array[i], i, i === ilen-1);
		}
	};

	var result = [];
	exec('git log --pretty="format:%cd|%cn|%ce|%s|%b" --no-merges', function(error, stdout, stderr) {
		if(error) {
			cb(stderr);

		} else {
			each(stdout.split('\n'), function(line, i, last) {
				var parts = line.split('|');
				if (parts.length == 4) {
					result.push({
						date: datehelper.humanShort(new Date(parts[0])),
						message: parts[3],
						author: parts[1],
						email: parts[2]
					});
				}
				if (last) {
					cb(null, result);
				}
			});
		}
	});
};

var tailFile = function(path, count, callback) {
	fs.readFile(path, function (err, data) {
		var lines = data.toString().split('\n');
		var result = [];
		for(var i = lines.length - 1; i > 0 || i > lines.length - lines; i--) {
			var commit = {};
			var split = lines[i].split(">");
			if (split.length == 2) {
				split = split[1].split(' ');
				if (split.length > 0) {
					commit.date = datehelper.humanShort(new Date(parseInt(split[1]) * 1000));
				} else {
					continue;
				}
			} else {
				continue;
			}
			var split = lines[i].split("commit:");
			if (split.length == 2) {
				commit.message = split[1];
			} else {
				continue;
			}
			result.push(commit);
		}
		callback(err, result);
	});
};<commit_msg>fix changelog<commit_after>var fs = require('fs');
var datehelper = require('../modules/datehelper');
var exec = require('child_process').exec;

module.exports = function(app, layout) {
	app.get('/changelog/?', function(req, res, next) {
		try {
			getGitLog(function (err, data) {
				layout.render(req, res, {
						pageTemplate: 'page_changelog',
						commits: data
					});
			});
		} catch (e) {
			layout.render(req, res, dialog({
				message: '¡Ups! Me he hecho caca',
				action: layout.getReturnUrl(req, true),
				error: true
			}).asGlobal());
			console.log(e, e.stack);
		}
	});
};

var getGitLog = function(cb) {
	var each = function(array, fn) {
		for(var i = 0, ilen = array.length; i < ilen; i++) {
			fn(array[i], i, i === ilen-1);
		}
	};

	var result = [];
	exec('git log --pretty="format:%cd|%cn|%ce|%s|%b" --no-merges', function(error, stdout, stderr) {
		if(error) {
			cb(stderr);

		} else {
			each(stdout.split('\n'), function(line, i, last) {
				var parts = line.split('|');
				if (parts.length >= 4) {
					result.push({
						date: datehelper.humanShort(new Date(parts[0])),
						message: parts[3],
						author: parts[1],
						email: parts[2]
					});
				}
				if (last) {
					cb(null, result);
				}
			});
		}
	});
};

var tailFile = function(path, count, callback) {
	fs.readFile(path, function (err, data) {
		var lines = data.toString().split('\n');
		var result = [];
		for(var i = lines.length - 1; i > 0 || i > lines.length - lines; i--) {
			var commit = {};
			var split = lines[i].split(">");
			if (split.length == 2) {
				split = split[1].split(' ');
				if (split.length > 0) {
					commit.date = datehelper.humanShort(new Date(parseInt(split[1]) * 1000));
				} else {
					continue;
				}
			} else {
				continue;
			}
			var split = lines[i].split("commit:");
			if (split.length == 2) {
				commit.message = split[1];
			} else {
				continue;
			}
			result.push(commit);
		}
		callback(err, result);
	});
};<|endoftext|>


<commit_before>"use strict";

(function() {
  var root = this;

  if (typeof Promise === 'undefined') {
    if (typeof require === 'function') {
      var Promise = require('promise/lib/es6-extensions');
    }
    else {
      throw new Error('Portals requires "Promise" support');
    }
  }

  /**
   * Merges multiple objects together into a single object and
   * returns said object.
   *
   * @param  {Object} out
   * @return {Object}
   */
  var deepExtend = function (out) {
    out = out || {};

    for (var i = 1; i < arguments.length; i++) {
      var obj = arguments[i];

      if (!obj) continue;

      for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
          if (typeof obj[key] === 'object') {
            out[key] = out[key] || {};
            deepExtend(out[key], obj[key]);
          }
          else {
            out[key] = obj[key];
          }
        }
      }
    }

    return out;
  }

  /**
   * Validates request options and ensures URL and method are provided.
   *
   * @param  {Object} options
   * @return {Object}
   */
  var validateRequestInterceptor = function (options) {
    // ensure method is present and a string
    if (typeof options.method !== 'string') {
      throw new Error('Invalid method provided');
    }

    // ensure url is present and a string
    if (typeof options.url !== 'string') {
      throw new Error('Invalid url provided');
    }

    return options
  }

  /**
   * Merges globals into each request.
   *
   * @param  {Object} options
   * @return {Object}
   */
  var mergeGlobalsRequestInterceptor = function (options) {
    options = deepExtend({}, this.globals, options);

    return options;
  }

  /**
   * Builds up the URL using the hostname, params and query.
   *
   * @param  {Object} options
   * @return {Object}
   */
  var buildUrlRequestInterceptor = function (options) {
    // if "http" is not present in url, add the hostname
    if (options.url.indexOf('http') !== 0) {
      options.url = (options.hostname || '') + options.url;
    }

    // TODO replace parameters in URL
    // TODO build query string from query object

    return options;
  }

  /**
   * Encodes the request body as JSON.
   *
   * @param  {Object} options
   * @return {Object}
   */
  var encodeJsonRequestInterceptor = function (options) {
    if (options.data
        && typeof options.headers['Content-Type'] === 'string'
        && options.headers['Content-Type'].indexOf('json') !== -1) {
      options.body = JSON.stringify(options.data || options.body);
    }

    return options;
  }

  /**
   * Parses the response if JSON.
   *
   * @param  {Object} response
   * @return {Object}
   */
  var parseJsonResponseInterceptor = function (response) {
    response.headers = response.headers || {};

    if (response.headers['Content-Type']
        && response.headers['Content-Type'].indexOf('json') !== -1) {
      response.body = JSON.parse(response.body);
    }

    return response;
  }

  /**
   * Constructor: Generate a new "Portal" instance.
   */
  var Portal = function () {
    this.globals = {
      hostname: '',
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    };

    this._requestInterceptors = [
      validateRequestInterceptor
    ];

    this._responseInterceptors = [];
  }

  /**
   * Send a request to the server using the given details in the
   * options object.  Inherits base object from the set globals.
   *
   * @param  {Object} options
   * @return {Promise}
   */
  Portal.prototype.send = function (opts) {
    // ensure that an object is given
    if (typeof opts !== 'object') {
      throw new Error('Options must be an object!');
    }

    // loop through the request interceptors
    for (var i = 0; i < this._requestInterceptors.length; i++) {
      opts = this._requestInterceptors[i].call(this, opts)

      // make sure that the options object is still an object
      if (typeof opts !== 'object') {
        throw new Error('Options object is no longer an object after interception!');
      }
    }

    // prepare the request
    var self = this;
    var xhr = new XMLHttpRequest();

    xhr.open(opts.method.toUpperCase(), opts.url, true);

    var promise = new Promise(function (resolve, reject) {
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          var response = {
            status: this.status,
            headers: this.responseHeaders,
            body: this.responseText
          };

          // loop through the response interceptors
          for (var i = 0; i < self._responseInterceptors.length; i++) {
            response = self._responseInterceptors[i].call(self, response)

            // make sure that the options object is still an object
            if (typeof response !== 'object') {
              throw new Error('Response object is no longer an object after interception!');
            }
          }

          if (this.status === 200) resolve(response);
          else reject(response);
        }
      }
    });

    // add the headers to the request
    for (var key in opts.headers) {
      xhr.setRequestHeader(key, opts.headers[key]);
    }

    xhr.send(opts.data || opts.body || null);

    return promise;
  }

  /**
   * Helper for making GET requests.
   *
   * @param  {String} url
   * @return {Promise}
   */
  Portal.prototype.get = function (url) {
    return this.send({
      method: 'GET',
      url: url
    });
  }

  /**
   * Helper for making POST requests.
   *
   * @param  {String} url
   * @param  {Object} data
   * @return {Promise}
   */
  Portal.prototype.post = function (url, data) {
    return this.send({
      method: 'POST',
      url: url,
      data: data
    });
  }

  /**
   * Helper for making PUT requests.
   *
   * @param  {String} url
   * @param  {Object} data
   * @return {Promise}
   */
  Portal.prototype.put = function (url, data) {
    return this.send({
      method: 'PUT',
      url: url,
      data: data
    });
  }

  /**
   * Helper for making DELETE requests.
   *
   * @param  {String} url
   * @return {Promise}
   */
  Portal.prototype.del = function (url) {
    return this.send({
      method: 'DELETE',
      url: url
    });
  }

  /**
   * Adds all the default interceptors to the instance.
   *
   * @return {this}
   */
  Portal.prototype.useDefaultInterceptors = function () {
    return this
      .onRequest(mergeGlobalsRequestInterceptor)
      .onRequest(buildUrlRequestInterceptor)
      .onRequest(encodeJsonRequestInterceptor)
      .onResponse(parseJsonResponseInterceptor);
  }

  /**
   * Add new request interceptor.
   *
   * @param  {Function} fn
   * @return {this}
   */
  Portal.prototype.onRequest = function (fn) {
    // ensure that interceptor is function
    if (typeof fn !== 'function') {
      throw new Error('Interceptor must be a function!');
    }

    // add the interceptor to the list
    this._requestInterceptors.push(fn);

    return this;
  }

  /**
   * Add new response interceptor.
   *
   * @param  {Function} fn
   * @return {this}
   */
  Portal.prototype.onResponse = function (fn) {
    // ensure that interceptor is function
    if (typeof fn !== 'function') {
      throw new Error('Interceptor must be a function!');
    }

    // add the interceptor to the list
    this._responseInterceptors.push(fn);

    return this;
  }

  /**
   * Factory method that creates a new Portal instance with the
   * default interceptors.
   *
   * @return {Portal}
   */
  var factory = function () {
    var port = new Portal();

    port.useDefaultInterceptors();

    return port;
  }

  /**
   * EXPORT
   * Export via CommonJS or attach it to the window.
   */
  var portals = {
    create: factory,
    Portal: Portal,
    interceptors: {
      validateRequest: validateRequestInterceptor,
      mergeGlobalsRequest: mergeGlobalsRequestInterceptor,
      buildUrlRequest: buildUrlRequestInterceptor,
      encodeJsonRequest: encodeJsonRequestInterceptor,
      parseJsonResponse: parseJsonResponseInterceptor
    }
  };

  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = portals
    }
    exports.portals = portals
  }
  else {
    root.portals = portals
  }

}).call(this)
<commit_msg>Moved "headers" object setting to the send() method directly<commit_after>"use strict";

(function() {
  var root = this;

  if (typeof Promise === 'undefined') {
    if (typeof require === 'function') {
      var Promise = require('promise/lib/es6-extensions');
    }
    else {
      throw new Error('Portals requires "Promise" support');
    }
  }

  /**
   * Merges multiple objects together into a single object and
   * returns said object.
   *
   * @param  {Object} out
   * @return {Object}
   */
  var deepExtend = function (out) {
    out = out || {};

    for (var i = 1; i < arguments.length; i++) {
      var obj = arguments[i];

      if (!obj) continue;

      for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
          if (typeof obj[key] === 'object') {
            out[key] = out[key] || {};
            deepExtend(out[key], obj[key]);
          }
          else {
            out[key] = obj[key];
          }
        }
      }
    }

    return out;
  }

  /**
   * Validates request options and ensures URL and method are provided.
   *
   * @param  {Object} options
   * @return {Object}
   */
  var validateRequestInterceptor = function (options) {
    // ensure method is present and a string
    if (typeof options.method !== 'string') {
      throw new Error('Invalid method provided');
    }

    // ensure url is present and a string
    if (typeof options.url !== 'string') {
      throw new Error('Invalid url provided');
    }

    return options
  }

  /**
   * Merges globals into each request.
   *
   * @param  {Object} options
   * @return {Object}
   */
  var mergeGlobalsRequestInterceptor = function (options) {
    options = deepExtend({}, this.globals, options);

    return options;
  }

  /**
   * Builds up the URL using the hostname, params and query.
   *
   * @param  {Object} options
   * @return {Object}
   */
  var buildUrlRequestInterceptor = function (options) {
    // if "http" is not present in url, add the hostname
    if (options.url.indexOf('http') !== 0) {
      options.url = (options.hostname || '') + options.url;
    }

    // TODO replace parameters in URL
    // TODO build query string from query object

    return options;
  }

  /**
   * Encodes the request body as JSON.
   *
   * @param  {Object} options
   * @return {Object}
   */
  var encodeJsonRequestInterceptor = function (options) {
    if (options.data
        && typeof options.headers['Content-Type'] === 'string'
        && options.headers['Content-Type'].indexOf('json') !== -1) {
      options.body = JSON.stringify(options.data || options.body);
    }

    return options;
  }

  /**
   * Parses the response if JSON.
   *
   * @param  {Object} response
   * @return {Object}
   */
  var parseJsonResponseInterceptor = function (response) {
    if (response.headers['Content-Type']
        && response.headers['Content-Type'].indexOf('json') !== -1) {
      response.body = JSON.parse(response.body);
    }

    return response;
  }

  /**
   * Constructor: Generate a new "Portal" instance.
   */
  var Portal = function () {
    this.globals = {
      hostname: '',
      method: 'GET',
      headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
      }
    };

    this._requestInterceptors = [
      validateRequestInterceptor
    ];

    this._responseInterceptors = [];
  }

  /**
   * Send a request to the server using the given details in the
   * options object.  Inherits base object from the set globals.
   *
   * @param  {Object} options
   * @return {Promise}
   */
  Portal.prototype.send = function (opts) {
    // ensure that an object is given
    if (typeof opts !== 'object') {
      throw new Error('Options must be an object!');
    }

    // loop through the request interceptors
    for (var i = 0; i < this._requestInterceptors.length; i++) {
      opts = this._requestInterceptors[i].call(this, opts)

      // make sure that the options object is still an object
      if (typeof opts !== 'object') {
        throw new Error('Options object is no longer an object after interception!');
      }
    }

    // prepare the request
    var self = this;
    var xhr = new XMLHttpRequest();

    xhr.open(opts.method.toUpperCase(), opts.url, true);

    var promise = new Promise(function (resolve, reject) {
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          var response = {
            status: this.status,
            headers: this.responseHeaders || {},
            body: this.responseText
          };

          // loop through the response interceptors
          for (var i = 0; i < self._responseInterceptors.length; i++) {
            response = self._responseInterceptors[i].call(self, response)

            // make sure that the options object is still an object
            if (typeof response !== 'object') {
              throw new Error('Response object is no longer an object after interception!');
            }
          }

          if (this.status === 200) resolve(response);
          else reject(response);
        }
      }
    });

    // add the headers to the request
    for (var key in opts.headers) {
      xhr.setRequestHeader(key, opts.headers[key]);
    }

    xhr.send(opts.data || opts.body || null);

    return promise;
  }

  /**
   * Helper for making GET requests.
   *
   * @param  {String} url
   * @return {Promise}
   */
  Portal.prototype.get = function (url) {
    return this.send({
      method: 'GET',
      url: url
    });
  }

  /**
   * Helper for making POST requests.
   *
   * @param  {String} url
   * @param  {Object} data
   * @return {Promise}
   */
  Portal.prototype.post = function (url, data) {
    return this.send({
      method: 'POST',
      url: url,
      data: data
    });
  }

  /**
   * Helper for making PUT requests.
   *
   * @param  {String} url
   * @param  {Object} data
   * @return {Promise}
   */
  Portal.prototype.put = function (url, data) {
    return this.send({
      method: 'PUT',
      url: url,
      data: data
    });
  }

  /**
   * Helper for making DELETE requests.
   *
   * @param  {String} url
   * @return {Promise}
   */
  Portal.prototype.del = function (url) {
    return this.send({
      method: 'DELETE',
      url: url
    });
  }

  /**
   * Adds all the default interceptors to the instance.
   *
   * @return {this}
   */
  Portal.prototype.useDefaultInterceptors = function () {
    return this
      .onRequest(mergeGlobalsRequestInterceptor)
      .onRequest(buildUrlRequestInterceptor)
      .onRequest(encodeJsonRequestInterceptor)
      .onResponse(parseJsonResponseInterceptor);
  }

  /**
   * Add new request interceptor.
   *
   * @param  {Function} fn
   * @return {this}
   */
  Portal.prototype.onRequest = function (fn) {
    // ensure that interceptor is function
    if (typeof fn !== 'function') {
      throw new Error('Interceptor must be a function!');
    }

    // add the interceptor to the list
    this._requestInterceptors.push(fn);

    return this;
  }

  /**
   * Add new response interceptor.
   *
   * @param  {Function} fn
   * @return {this}
   */
  Portal.prototype.onResponse = function (fn) {
    // ensure that interceptor is function
    if (typeof fn !== 'function') {
      throw new Error('Interceptor must be a function!');
    }

    // add the interceptor to the list
    this._responseInterceptors.push(fn);

    return this;
  }

  /**
   * Factory method that creates a new Portal instance with the
   * default interceptors.
   *
   * @return {Portal}
   */
  var factory = function () {
    var port = new Portal();

    port.useDefaultInterceptors();

    return port;
  }

  /**
   * EXPORT
   * Export via CommonJS or attach it to the window.
   */
  var portals = {
    create: factory,
    Portal: Portal,
    interceptors: {
      validateRequest: validateRequestInterceptor,
      mergeGlobalsRequest: mergeGlobalsRequestInterceptor,
      buildUrlRequest: buildUrlRequestInterceptor,
      encodeJsonRequest: encodeJsonRequestInterceptor,
      parseJsonResponse: parseJsonResponseInterceptor
    }
  };

  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = portals
    }
    exports.portals = portals
  }
  else {
    root.portals = portals
  }

}).call(this)
<|endoftext|>


<commit_before>describe('Stimuli.virtual.Browser', function() {

    describe('navigateTo', function() {

        it('should be able to navigate to several urls in sequence', function(done) {
            var browser = new Stimuli.virtual.Browser();
            var viewport = new Stimuli.view.Viewport();
            browser.viewport = viewport;

            browser
                .navigateTo('/base/test/fixtures/empty.html')
                .then(function() {
                    expect(/empty\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .navigateTo('/base/test/fixtures/links.html')
                .then(function() {
                    expect(/links\.html$/.test(viewport.getContext().location + '')).to.be(true);

                })
                .destroy(function() {
                    done();
                });
        });

        it('should throw an error if the page doesn\'t exist', function(done) {
                var browser = new Stimuli.virtual.Browser();
                browser.viewport = new Stimuli.view.Viewport();

                var backupOnError = window.onerror;
                window.onerror = function(e) {
                    window.onerror = backupOnError;
                    setTimeout(function() {
                        browser.destroy();
                        expect(e.toString()).to.contain('Stimuli.browser: Unable to navigate to url. (404) Not Found');
                        done();
                    }, 1)
                };

                browser.navigateTo('/undefined.undefined');
        });

    });

    describe('back and forward', function() {

        it('should be able to navigate to go back in history', function(done) {
            var browser = new Stimuli.virtual.Browser();
            var viewport = new Stimuli.view.Viewport();
            browser.viewport = viewport;

            browser
                .navigateTo('/base/test/fixtures/empty.html')
                .then(function() {
                    expect(/empty\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .navigateTo('/base/test/fixtures/links.html')
                .then(function() {
                    expect(/links\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .back()
                .then(function() {
                    expect(/empty\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .forward()
                .then(function() {
                    expect(/links\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .destroy(function() {
                    done();
                });
        });

    });

    describe('back only', function() {

        it('should throw an error if it\'s impossible to go back', function(done) {
            var browser = new Stimuli.virtual.Browser();
            var viewport = new Stimuli.view.Viewport();
            browser.viewport = viewport;

            var backupOnError = window.onerror;
            window.onerror = function(e) {
                window.onerror = backupOnError;
                setTimeout(function() {
                    browser.destroy();
                    expect(e.toString()).to.contain('Stimuli.browser: Can\'t go back there is no history.');
                    done();
                }, 1)
            };

            browser
                .navigateTo('/base/test/fixtures/empty.html')
                .back()
                .forward()
                .back()
                .back();

        });

    });

    describe('forward only', function() {

        it('should throw an error if it\'s impossible to go forward', function(done) {
            var browser = new Stimuli.virtual.Browser();
            var viewport = new Stimuli.view.Viewport();
            browser.viewport = viewport;

            var backupOnError = window.onerror;

            window.onerror = function(e) {
                window.onerror = backupOnError;
                setTimeout(function() {
                    browser.destroy();
                    expect(e.toString()).to.contain('Stimuli.browser: Can\'t go forward there is no history.');
                    done();
                }, 1)
            };

            browser
                .navigateTo('/base/test/fixtures/empty.html')
                .back()
                .navigateTo('/base/test/fixtures/links.html')
                .back()
                .forward()
                .forward();

        });
    });

});<commit_msg>test: comments some tricky asynchronous error throwing detection and fixes some lint issues<commit_after>describe('Stimuli.virtual.Browser', function() {

    describe('navigateTo', function() {

        it('should be able to navigate to several urls in sequence', function(done) {
            var browser = new Stimuli.virtual.Browser();
            var viewport = new Stimuli.view.Viewport();
            browser.viewport = viewport;

            browser
                .navigateTo('/base/test/fixtures/empty.html')
                .then(function() {
                    expect(/empty\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .navigateTo('/base/test/fixtures/links.html')
                .then(function() {
                    expect(/links\.html$/.test(viewport.getContext().location + '')).to.be(true);

                })
                .destroy(function() {
                    done();
                });
        });

        it('should throw an error if the page doesn\'t exist', function(done) {
                var browser = new Stimuli.virtual.Browser();
                browser.viewport = new Stimuli.view.Viewport();

                var backupOnError = window.onerror;
                window.onerror = function(e) {
                    window.onerror = backupOnError;
                    setTimeout(function() { // the expectation test will be executed outside onerror
                        browser.destroy();
                        expect(e.toString()).to.contain('Stimuli.browser: Unable to navigate to url. (404) Not Found');
                        done();
                    }, 1);
                };

                browser.navigateTo('/undefined.undefined');
        });

    });

    describe('back and forward', function() {

        it('should be able to navigate to go back in history', function(done) {
            var browser = new Stimuli.virtual.Browser();
            var viewport = new Stimuli.view.Viewport();
            browser.viewport = viewport;

            browser
                .navigateTo('/base/test/fixtures/empty.html')
                .then(function() {
                    expect(/empty\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .navigateTo('/base/test/fixtures/links.html')
                .then(function() {
                    expect(/links\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .back()
                .then(function() {
                    expect(/empty\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .forward()
                .then(function() {
                    expect(/links\.html$/.test(viewport.getContext().location + '')).to.be(true);
                })
                .destroy(function() {
                    done();
                });
        });

    });

    describe('back only', function() {

        it('should throw an error if it\'s impossible to go back', function(done) {
            var browser = new Stimuli.virtual.Browser();
            var viewport = new Stimuli.view.Viewport();
            browser.viewport = viewport;

            var backupOnError = window.onerror;
            window.onerror = function(e) {
                window.onerror = backupOnError;
                setTimeout(function() {  // the expectation test will be executed outside onerror
                    browser.destroy();
                    expect(e.toString()).to.contain('Stimuli.browser: Can\'t go back there is no history.');
                    done();
                }, 1);
            };

            browser
                .navigateTo('/base/test/fixtures/empty.html')
                .back()
                .forward()
                .back()
                .back();

        });

    });

    describe('forward only', function() {

        it('should throw an error if it\'s impossible to go forward', function(done) {
            var browser = new Stimuli.virtual.Browser();
            var viewport = new Stimuli.view.Viewport();
            browser.viewport = viewport;

            var backupOnError = window.onerror;

            window.onerror = function(e) {
                window.onerror = backupOnError;
                setTimeout(function() { // the expectation test will be executed outside onerror
                    browser.destroy();
                    expect(e.toString()).to.contain('Stimuli.browser: Can\'t go forward there is no history.');
                    done();
                }, 1);
            };

            browser
                .navigateTo('/base/test/fixtures/empty.html')
                .back()
                .navigateTo('/base/test/fixtures/links.html')
                .back()
                .forward()
                .forward();

        });
    });

});<|endoftext|>


<commit_before><commit_msg>Updated tests for dateInput<commit_after><|endoftext|>


<commit_before>"use strict"

var module = module || {};

function Grid (context) {
  this.origin        = { q: 0, r: 0 };
  this.context       = context;

  this.gridRings      = 1;
  this.gridDrawOrigin = true;

  this.tileSpacing   = 0;
  this.tilePointy    = true;
  this.tileSize      = 100;
  this.tileLineColor = "rgba(0,0,0,1)";
  this.tileLineWidth = 1;
  this.tileColor     = "rgba(0,0,0,0)";
}

Grid.prototype.drawRingGrid = function (q, r) {
  // Used below to move the to the next tile positioning and drawing
  var moveDirections = [
  //  q   r
    [ 1,  0],
    [ 0, -1],
    [-1,  0],
    [-1,  1],
    [ 0,  1],
    [ 1,  0],
    [ 1, -1]
  ];

  // Set grid center
  var currentQ = q;
  var currentR = r;

  // Draw grid center?
  if (this.gridDrawOrigin) {
    this.drawTile(q, r);
  }

  // Traverse through ring "currentRing" starting with ring one (1)
  for (var currentRing = 1; currentRing <= this.gridRings; currentRing++) {
    // Reset origin for the draw loop
    currentQ = q;
    currentR = r;

    // Move east and draw, then move south-west and draw, then move west and draw and so forth.
    for(var moveDirection = 0; moveDirection < moveDirections.length; moveDirection++) {

      // How many times we have to move in each direction to draw the entire ring
      for(var moveCounter = 0; moveCounter < currentRing; moveCounter++) {
        // Move into position
        currentQ += moveDirections[moveDirection][0];
        currentR += moveDirections[moveDirection][1];

        // The first move is for getting into position, not drawing
        if (moveDirection != 0) {
          // Draw tile for position
          this.drawTile(currentQ, currentR);
        }
      }
    }
  }
};

Grid.prototype.getCenterXY = function (q, r) {
  var x, y;

  if (this.tilePointy) {
    x = (this.tileSize + this.tileSpacing) * Math.sqrt(3) * (q + r / 2);
    y = -((this.tileSize + this.tileSpacing) * 3/2 * r);
  } else {
    x = (this.tileSize + this.tileSpacing) * 3/2 * q;
    y = -((this.tileSize + this.tileSpacing) * Math.sqrt(3) * (r + q / 2));
  }

  return { x: x, y: y };
};

Grid.prototype.drawTile = function (q, r) {
  var centers = this.getCenterXY(q, r),
      centerX = centers.x,
      centerY = centers.y,
      tileAngle = 0;

  this.context.save();
  this.context.beginPath();

  this.context.lineWidth = this.tileLineWidth;
  this.context.strokeStyle = this.tileLineColor;

  if (this.tilePointy) {
    tileAngle = 0.5;
  }

  for (var iCorner = 0; iCorner <= 6; iCorner++) {
    var angle   = 2 * Math.PI / 6 * (iCorner + tileAngle),
        cornerX = centerX + this.tileSize * Math.cos(angle),
        cornerY = centerY + this.tileSize * Math.sin(angle);

    if (iCorner > 0) {
      this.context.lineTo(cornerX, cornerY);
    } else {
      this.context.moveTo(cornerX, cornerY);
    }
  }

  this.context.stroke();
  this.context.fillStyle = this.tileColor;
  this.context.fill();

  this.context.closePath();
  this.context.restore();
};

module.exports = Grid;
<commit_msg>Removed closePath as the start and end points are the same coordinates.<commit_after>"use strict"

var module = module || {};

function Grid (context) {
  this.origin        = { q: 0, r: 0 };
  this.context       = context;

  this.gridRings      = 1;
  this.gridDrawOrigin = true;

  this.tileSpacing   = 0;
  this.tilePointy    = true;
  this.tileSize      = 100;
  this.tileLineColor = "rgba(0,0,0,1)";
  this.tileLineWidth = 1;
  this.tileColor     = "rgba(0,0,0,0)";
}

Grid.prototype.drawRingGrid = function (q, r) {
  // Used below to move the to the next tile positioning and drawing
  var moveDirections = [
  //  q   r
    [ 1,  0],
    [ 0, -1],
    [-1,  0],
    [-1,  1],
    [ 0,  1],
    [ 1,  0],
    [ 1, -1]
  ];

  // Set grid center
  var currentQ = q;
  var currentR = r;

  // Draw grid center?
  if (this.gridDrawOrigin) {
    this.drawTile(q, r);
  }

  // Traverse through ring "currentRing" starting with ring one (1)
  for (var currentRing = 1; currentRing <= this.gridRings; currentRing++) {
    // Reset origin for the draw loop
    currentQ = q;
    currentR = r;

    // Move east and draw, then move south-west and draw, then move west and draw and so forth.
    for(var moveDirection = 0; moveDirection < moveDirections.length; moveDirection++) {

      // How many times we have to move in each direction to draw the entire ring
      for(var moveCounter = 0; moveCounter < currentRing; moveCounter++) {
        // Move into position
        currentQ += moveDirections[moveDirection][0];
        currentR += moveDirections[moveDirection][1];

        // The first move is for getting into position, not drawing
        if (moveDirection != 0) {
          // Draw tile for position
          this.drawTile(currentQ, currentR);
        }
      }
    }
  }
};

Grid.prototype.getCenterXY = function (q, r) {
  var x, y;

  if (this.tilePointy) {
    x = (this.tileSize + this.tileSpacing) * Math.sqrt(3) * (q + r / 2);
    y = -((this.tileSize + this.tileSpacing) * 3/2 * r);
  } else {
    x = (this.tileSize + this.tileSpacing) * 3/2 * q;
    y = -((this.tileSize + this.tileSpacing) * Math.sqrt(3) * (r + q / 2));
  }

  return { x: x, y: y };
};

Grid.prototype.drawTile = function (q, r) {
  var centers = this.getCenterXY(q, r),
      centerX = centers.x,
      centerY = centers.y,
      tileAngle = 0;

  this.context.save();
  this.context.beginPath();

  this.context.lineWidth = this.tileLineWidth;
  this.context.strokeStyle = this.tileLineColor;

  if (this.tilePointy) {
    tileAngle = 0.5;
  }

  for (var iCorner = 0; iCorner <= 6; iCorner++) {
    var angle   = 2 * Math.PI / 6 * (iCorner + tileAngle),
        cornerX = centerX + this.tileSize * Math.cos(angle),
        cornerY = centerY + this.tileSize * Math.sin(angle);

    if (iCorner > 0) {
      this.context.lineTo(cornerX, cornerY);
    } else {
      this.context.moveTo(cornerX, cornerY);
    }
  }

  this.context.stroke();
  this.context.fillStyle = this.tileColor;
  this.context.fill();

  this.context.restore();
};

module.exports = Grid;
<|endoftext|>


<commit_before>module.exports = {
  'plugins': [
    'react'
  ],
  'ecmaFeatures': {
    'jsx': true
  },
  // View link below for react rules documentation
  // https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules
  'rules': {
    // Prevent missing displayName in a React component definition
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md
    'react/display-name': [0, { 'ignoreTranspilerName': false }],
    // Forbid certain propTypes (any, array, object)
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-prop-types.md
    'react/forbid-prop-types': [0, { 'forbid': ['any', 'array', 'object'] }],
    // Enforce boolean attributes notation in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md
    'react/jsx-boolean-value': [2, 'never'],
    // Validate closing bracket location in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md
    'react/jsx-closing-bracket-location': [2, 'line-aligned'],
    // Enforce or disallow spaces inside of curly braces in JSX attributes
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md
    'react/jsx-curly-spacing': [0, 'never', { 'allowMultiline': true }],
    // Enforce event handler naming conventions in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md
    'react/jsx-handler-names': [0, {
      'eventHandlerPrefix': 'handle',
      'eventHandlerPropPrefix': 'on',
    }],
    // Validate props indentation in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md
    'react/jsx-indent-props': [2, 2],
    // Validate JSX has key prop when in array or iterator
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md
    'react/jsx-key': 0,
    // Limit maximum of props on a single line in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md
    'react/jsx-max-props-per-line': [0, { 'maximum': 1 }],
    // Prevent usage of .bind() and arrow functions in JSX props
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
    'react/jsx-no-bind': 2,
    // Prevent duplicate props in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md
    'react/jsx-no-duplicate-props': [0, { 'ignoreCase': false }],
    // Prevent usage of unwrapped JSX strings
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md
    'react/jsx-no-literals': 0,
    // Disallow undeclared variables in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md
    'react/jsx-no-undef': 2,
    // Enforce PascalCase for user-defined JSX components
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
    'react/jsx-pascal-case': 0,
    // Enforce propTypes declarations alphabetical sorting
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-prop-types.md
    'react/sort-prop-types': [0, {
      'ignoreCase': false,
      'callbacksLast': false,
    }],
    // Enforce props alphabetical sorting
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md
    'react/jsx-sort-props': [0, {
      'ignoreCase': false,
      'callbacksLast': false,
    }],
    // Prevent React to be incorrectly marked as unused
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
    'react/jsx-uses-react': [2, { 'pragma': 'React' }],
    // Prevent variables used in JSX to be incorrectly marked as unused
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md
    'react/jsx-uses-vars': 2,
    // Prevent usage of dangerous JSX properties
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md
    'react/no-danger': 0,
    // Prevent usage of deprecated methods
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md
    'react/no-deprecated': [1, { 'react': '0.14.0' }],
    // Prevent usage of setState in componentDidMount
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md
    'react/no-did-mount-set-state': [2, 'allow-in-func'],
    // Prevent usage of setState in componentDidUpdate
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md
    'react/no-did-update-set-state': [2, 'allow-in-func'],
    // Prevent direct mutation of this.state
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md
    'react/no-direct-mutation-state': 0,
    // Prevent usage of isMounted
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md
    'react/no-is-mounted': 2,
    // Prevent multiple component definition per file
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md
    'react/no-multi-comp': [2, { 'ignoreStateless': true }],
    // Prevent usage of setState
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-set-state.md
    'react/no-set-state': 0,
    // Prevent using string references
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md
    'react/no-string-refs': 0,
    // Prevent usage of unknown DOM property
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md
    'react/no-unknown-property': 2,
    // Require ES6 class declarations over React.createClass
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md
    'react/prefer-es6-class': [2, 'always'],
    // Require stateless functions when not using lifecycle methods, setState or ref
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md
    'react/prefer-stateless-function': 2,
    // Prevent missing props validation in a React component definition
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md
    'react/prop-types': [2, { 'ignore': [], 'customValidators': [] }],
    // Prevent missing React when using JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
    'react/react-in-jsx-scope': 2,
    // Restrict file extensions that may be required
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-extension.md
    'react/require-extension': [0, { 'extensions': ['.jsx'] }],
    // Prevent extra closing tags for components without children
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
    'react/self-closing-comp': 2,
    // Enforce spaces before the closing bracket of self-closing JSX elements
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-space-before-closing.md
    'react/jsx-space-before-closing': [2, 'always'],
    // Enforce component methods order
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
    'react/sort-comp': [2, {
      'order': [
        'static-methods',
        'lifecycle',
        '/^on.+$/',
        '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/',
        'everything-else',
        '/^render.+$/',
        'render'
      ]
    }],
    // Prevent missing parentheses around multilines JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md
    'react/wrap-multilines': [2, {
      declaration: true,
      assignment: true,
      return: true
    }],
  }
};
<commit_msg>Add defaults for `react/jsx-no-bind`<commit_after>module.exports = {
  'plugins': [
    'react'
  ],
  'ecmaFeatures': {
    'jsx': true
  },
  // View link below for react rules documentation
  // https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules
  'rules': {
    // Prevent missing displayName in a React component definition
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md
    'react/display-name': [0, { 'ignoreTranspilerName': false }],
    // Forbid certain propTypes (any, array, object)
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-prop-types.md
    'react/forbid-prop-types': [0, { 'forbid': ['any', 'array', 'object'] }],
    // Enforce boolean attributes notation in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md
    'react/jsx-boolean-value': [2, 'never'],
    // Validate closing bracket location in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md
    'react/jsx-closing-bracket-location': [2, 'line-aligned'],
    // Enforce or disallow spaces inside of curly braces in JSX attributes
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md
    'react/jsx-curly-spacing': [0, 'never', { 'allowMultiline': true }],
    // Enforce event handler naming conventions in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md
    'react/jsx-handler-names': [0, {
      'eventHandlerPrefix': 'handle',
      'eventHandlerPropPrefix': 'on',
    }],
    // Validate props indentation in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md
    'react/jsx-indent-props': [2, 2],
    // Validate JSX has key prop when in array or iterator
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md
    'react/jsx-key': 0,
    // Limit maximum of props on a single line in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md
    'react/jsx-max-props-per-line': [0, { 'maximum': 1 }],
    // Prevent usage of .bind() and arrow functions in JSX props
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
    'react/jsx-no-bind': [2, {
      'ignoreRefs': false,
      'allowArrowFunctions': false,
      'allowBind': false,
    }],
    // Prevent duplicate props in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md
    'react/jsx-no-duplicate-props': [0, { 'ignoreCase': false }],
    // Prevent usage of unwrapped JSX strings
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md
    'react/jsx-no-literals': 0,
    // Disallow undeclared variables in JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md
    'react/jsx-no-undef': 2,
    // Enforce PascalCase for user-defined JSX components
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
    'react/jsx-pascal-case': 0,
    // Enforce propTypes declarations alphabetical sorting
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-prop-types.md
    'react/sort-prop-types': [0, {
      'ignoreCase': false,
      'callbacksLast': false,
    }],
    // Enforce props alphabetical sorting
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md
    'react/jsx-sort-props': [0, {
      'ignoreCase': false,
      'callbacksLast': false,
    }],
    // Prevent React to be incorrectly marked as unused
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
    'react/jsx-uses-react': [2, { 'pragma': 'React' }],
    // Prevent variables used in JSX to be incorrectly marked as unused
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md
    'react/jsx-uses-vars': 2,
    // Prevent usage of dangerous JSX properties
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md
    'react/no-danger': 0,
    // Prevent usage of deprecated methods
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md
    'react/no-deprecated': [1, { 'react': '0.14.0' }],
    // Prevent usage of setState in componentDidMount
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md
    'react/no-did-mount-set-state': [2, 'allow-in-func'],
    // Prevent usage of setState in componentDidUpdate
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md
    'react/no-did-update-set-state': [2, 'allow-in-func'],
    // Prevent direct mutation of this.state
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md
    'react/no-direct-mutation-state': 0,
    // Prevent usage of isMounted
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md
    'react/no-is-mounted': 2,
    // Prevent multiple component definition per file
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md
    'react/no-multi-comp': [2, { 'ignoreStateless': true }],
    // Prevent usage of setState
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-set-state.md
    'react/no-set-state': 0,
    // Prevent using string references
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md
    'react/no-string-refs': 0,
    // Prevent usage of unknown DOM property
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md
    'react/no-unknown-property': 2,
    // Require ES6 class declarations over React.createClass
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md
    'react/prefer-es6-class': [2, 'always'],
    // Require stateless functions when not using lifecycle methods, setState or ref
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md
    'react/prefer-stateless-function': 2,
    // Prevent missing props validation in a React component definition
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md
    'react/prop-types': [2, { 'ignore': [], 'customValidators': [] }],
    // Prevent missing React when using JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
    'react/react-in-jsx-scope': 2,
    // Restrict file extensions that may be required
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-extension.md
    'react/require-extension': [0, { 'extensions': ['.jsx'] }],
    // Prevent extra closing tags for components without children
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
    'react/self-closing-comp': 2,
    // Enforce spaces before the closing bracket of self-closing JSX elements
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-space-before-closing.md
    'react/jsx-space-before-closing': [2, 'always'],
    // Enforce component methods order
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
    'react/sort-comp': [2, {
      'order': [
        'static-methods',
        'lifecycle',
        '/^on.+$/',
        '/^(get|set)(?!(InitialState$|DefaultProps$|ChildContext$)).+$/',
        'everything-else',
        '/^render.+$/',
        'render'
      ]
    }],
    // Prevent missing parentheses around multilines JSX
    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md
    'react/wrap-multilines': [2, {
      declaration: true,
      assignment: true,
      return: true
    }],
  }
};
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { UIDatabase } from '../../../database';
import { parsePositiveInteger, MODAL_KEYS } from '../../../utilities';
import {
  getIndicatorRow,
  getIndicatorColumn,
  updateIndicatorValue,
  getIndicatorRowColumnValue,
} from '../../../database/utilities/getIndicatorData';
import { ACTIONS } from './constants';
import { openModal, closeModal } from './pageActions';
import { pageStateSelector } from '../selectors/pageSelectors';

/**
 * Refreshes a row in the DataTable component.
 * Use case: editing a field of a realm object and ensuring the
 * UI is updated.
 *
 * @param {String} rowKey Key of the row to edit.
 */
export const refreshRow = (rowKey, route) => ({
  type: ACTIONS.REFRESH_ROW,
  payload: { rowKey, route },
});

export const refreshIndicatorRow = route => ({
  type: 'refreshIndicatorRow',
  payload: { route },
});

/**
 * Edits a rows underlying `batch` field.
 *
 * @param {Date}    value       The new batch name value.
 * @param {String}  rowKey      Key of the row to edit.
 * @param {String}  objectType  Type of object to edit i.e. 'TransactionBatch'
 */
export const editBatchName = (value, rowKey, objectType, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  const { batch } = objectToEdit;

  if (value !== batch) {
    UIDatabase.write(() => UIDatabase.update(objectType, { ...objectToEdit, batch: value }));

    dispatch(refreshRow(rowKey, route));
  }
};

export const editIndicatorValue = (value, rowKey, columnKey, route) => (dispatch, getState) => {
  const { indicatorRows, indicatorColumns, pageObject } = pageStateSelector(getState());
  const { period } = pageObject;
  const row = getIndicatorRow(indicatorRows, rowKey);
  const column = getIndicatorColumn(indicatorColumns, columnKey);
  const indicatorValue = getIndicatorRowColumnValue(row, column, period);
  updateIndicatorValue(indicatorValue, value);
  dispatch(refreshIndicatorRow(route));
};

/**
 * Wrapper around editBatchName for StocktakeBatches
 */
export const editStocktakeBatchName = (value, rowKey, route) =>
  editBatchName(value, rowKey, 'StocktakeBatch', route);

/**
 * Edits a rows underlying `expiryDate` field.
 *
 * @param {Date}    newDate     The new date to set as the expiry.
 * @param {String}  rowKey      Key of the row to edit.
 * @param {String}  objectType  Type of object to edit i.e. 'TransactionBatch'
 */
export const editExpiryDate = (newDate, rowKey, objectType, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  UIDatabase.write(() => {
    objectToEdit.expiryDate = newDate;
    UIDatabase.save(objectType, objectToEdit);
  });

  dispatch(refreshRow(rowKey, route));
};

/**
 * Wrapper around editExpiryDate.
 */
export const editTransactionBatchExpiryDate = (newDate, rowKey, route) =>
  editExpiryDate(newDate, rowKey, 'TransactionBatch', route);

export const editStocktakeBatchExpiryDate = (newDate, rowKey, route) =>
  editExpiryDate(newDate, rowKey, 'StocktakeBatch', route);

/**
 * Edits the field `totalQuantity` of a rows underlying data object.
 *
 * @param {String|Number} value      The new value to set (parsed as a positive integer)
 * @param {String}        rowKey     The key of the row to edit.
 */
export const editTotalQuantity = (value, rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  UIDatabase.write(() => {
    objectToEdit.setTotalQuantity(UIDatabase, parsePositiveInteger(value));
  });

  dispatch(refreshRow(rowKey, route));
};

/**
 * Edits a rows underlying `suggestedQuantity` field.
 *
 * @param {String|Number}   value       The new value to set as the required quantity.
 * @param {String}          rowKey      Key of the row to edit.
 * @param {String}          objectType  Type of object to edit i.e. 'RequisitionItem'
 */
export const editSuppliedQuantity = (value, rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  UIDatabase.write(() => objectToEdit.setSuppliedQuantity(UIDatabase, parsePositiveInteger(value)));

  dispatch(refreshRow(rowKey, route));
};

/**
 * Edits a rows underlying `requiredQuantity` field.
 *
 * @param {String|Number}   value       The new value to set as the required quantity.
 * @param {String}          rowKey      Key of the row to edit.
 * @param {String}          objectType  Type of object to edit i.e. 'RequisitionItem'
 */
export const editRequiredQuantity = (value, rowKey, objectType, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  UIDatabase.write(() => {
    objectToEdit.requiredQuantity = parsePositiveInteger(value);
    UIDatabase.save(objectType, objectToEdit);
  });

  dispatch(refreshRow(rowKey, route));
};

/**
 * Wrapper around editRequiredQuantity.
 */
export const editRequisitionItemRequiredQuantity = (value, rowKey, route) =>
  editRequiredQuantity(value, rowKey, 'RequisitionItem', route);

/**
 * Edits a rows underlying countedTotalQuantity field.
 *
 * @param {String|Number}   value   The new value to set as the ccounted total quantity.
 * @param {String}          rowKey  Key of the row to edit.
 */
export const editCountedQuantity = (value, rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  objectToEdit.setCountedTotalQuantity(UIDatabase, parsePositiveInteger(value));

  dispatch(refreshRow(rowKey, route));
};

/**
 * Edits a StocktakeBatches underlying `countedTotalQuantity`
 *
 * @param {String|Number}   value  New value for the underlying `countedTotalQuantity` field
 * @param {String}          rowKey Key of the row to edit.
 */
export const editStocktakeBatchCountedQuantity = (value, rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  UIDatabase.write(() => {
    objectToEdit.countedTotalQuantity = parsePositiveInteger(value);
    UIDatabase.save('StocktakeBatch', UIDatabase);
  });

  dispatch(refreshRow(rowKey, route));
};

/**
 * Removes a reason from a rows underlying data.
 *
 * @param {String} rowKey   Key for the row to edit.
 */
export const removeReason = (rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  objectToEdit.removeReason(UIDatabase);

  dispatch(refreshRow(rowKey, route));
};

/**
 * Handles reason logic for a particular object (stocktakeBatch or
 * StocktakeItem) - if there is a difference (between snapshot and
 * countedTotalQuantity) - then a reason should be related to this
 * object. For negative adjustments, a negativeInventoryAdjustment
 * reason should be applied. If positive, a positiveInventoryAdjustment.
 * A correct reason
 * If there is already a reason, do nothing. If there is no
 * difference, but a reason has been previously applied, remove it.
 *
 * @param {String} rowKey Key of the row to enforce a reason on
 */
export const enforceReasonChoice = (rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);
  if (!objectToEdit) return null;

  const { difference } = objectToEdit;
  // If there's no difference, just remove the reason
  if (!difference) return dispatch(removeReason(rowKey, route));

  const { validateReason } = objectToEdit;
  if (!validateReason) {
    return dispatch(openModal(MODAL_KEYS.ENFORCE_STOCKTAKE_REASON, rowKey, route));
  }
  return null;
};

/**
 * Applys a passed reason to the underlying row data. Can be a StocktakeItem
 * or StocktakeBatch.
 *
 * @param {Realm.Option} value Reason to apply to the underlying rorw.
 */
export const applyReason = (value, route) => (dispatch, getState) => {
  const { modalValue, keyExtractor } = pageStateSelector(getState());

  modalValue.applyReason(UIDatabase, value);

  const rowKey = keyExtractor(modalValue);

  dispatch(closeModal(route));
  dispatch(refreshRow(rowKey, route));
};

/**
 * Wrapper around `editCountedTotalQuantity`, splitting the action to enforce a
 * reason also.
 *
 * @param {String|Number}   value  New value for the underlying `countedTotalQuantity` field
 * @param {String}          rowKey Key of the row to edit.
 */
export const editCountedQuantityWithReason = (value, rowKey, route) => dispatch => {
  dispatch(editCountedQuantity(value, rowKey, route));
  dispatch(enforceReasonChoice(rowKey, route));
};

/**
 * Wrapper around `editStocktakeBatchCountedQuantity`, splitting the action to enforce a
 * reason also.
 *
 * @param {String|Number}   value  New value for the underlying `countedTotalQuantity` field
 * @param {String}          rowKey Key of the row to edit.
 */
export const editStocktakeBatchCountedQuantityWithReason = (value, rowKey, route) => dispatch => {
  dispatch(editStocktakeBatchCountedQuantity(value, rowKey, route));
  dispatch(enforceReasonChoice(rowKey, route));
};

export const CellActionsLookup = {
  refreshRow,
  refreshIndicatorRow,
  editExpiryDate,
  editTransactionBatchExpiryDate,
  editStocktakeBatchExpiryDate,
  editTotalQuantity,
  editSuppliedQuantity,
  editRequiredQuantity,
  editRequisitionItemRequiredQuantity,
  editCountedQuantity,
  editStocktakeBatchCountedQuantity,
  removeReason,
  enforceReasonChoice,
  applyReason,
  editBatchName,
  editIndicatorValue,
  editStocktakeBatchName,
  editCountedQuantityWithReason,
  editStocktakeBatchCountedQuantityWithReason,
};
<commit_msg>Add defensive cellActions code<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { UIDatabase } from '../../../database';
import { parsePositiveInteger, MODAL_KEYS } from '../../../utilities';
import {
  getIndicatorRow,
  getIndicatorColumn,
  updateIndicatorValue,
  getIndicatorRowColumnValue,
} from '../../../database/utilities/getIndicatorData';
import { ACTIONS } from './constants';
import { openModal, closeModal } from './pageActions';
import { pageStateSelector } from '../selectors/pageSelectors';

/**
 * Refreshes a row in the DataTable component.
 * Use case: editing a field of a realm object and ensuring the
 * UI is updated.
 *
 * @param {String} rowKey Key of the row to edit.
 */
export const refreshRow = (rowKey, route) => ({
  type: ACTIONS.REFRESH_ROW,
  payload: { rowKey, route },
});

export const refreshIndicatorRow = route => ({
  type: 'refreshIndicatorRow',
  payload: { route },
});

/**
 * Edits a rows underlying `batch` field.
 *
 * @param {Date}    value       The new batch name value.
 * @param {String}  rowKey      Key of the row to edit.
 * @param {String}  objectType  Type of object to edit i.e. 'TransactionBatch'
 */
export const editBatchName = (value, rowKey, objectType, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  if (objectToEdit) {
    const { batch } = objectToEdit;

    if (value !== batch) {
      UIDatabase.write(() => UIDatabase.update(objectType, { ...objectToEdit, batch: value }));

      dispatch(refreshRow(rowKey, route));
    }
  }
};

export const editIndicatorValue = (value, rowKey, columnKey, route) => (dispatch, getState) => {
  const { indicatorRows, indicatorColumns, pageObject } = pageStateSelector(getState());
  const { period } = pageObject;
  const row = getIndicatorRow(indicatorRows, rowKey);
  const column = getIndicatorColumn(indicatorColumns, columnKey);
  const indicatorValue = getIndicatorRowColumnValue(row, column, period);
  updateIndicatorValue(indicatorValue, value);
  dispatch(refreshIndicatorRow(route));
};

/**
 * Wrapper around editBatchName for StocktakeBatches
 */
export const editStocktakeBatchName = (value, rowKey, route) =>
  editBatchName(value, rowKey, 'StocktakeBatch', route);

/**
 * Edits a rows underlying `expiryDate` field.
 *
 * @param {Date}    newDate     The new date to set as the expiry.
 * @param {String}  rowKey      Key of the row to edit.
 * @param {String}  objectType  Type of object to edit i.e. 'TransactionBatch'
 */
export const editExpiryDate = (newDate, rowKey, objectType, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  if (objectToEdit) {
    UIDatabase.write(() => {
      objectToEdit.expiryDate = newDate;
      UIDatabase.save(objectType, objectToEdit);
    });

    dispatch(refreshRow(rowKey, route));
  }
};

/**
 * Wrapper around editExpiryDate.
 */
export const editTransactionBatchExpiryDate = (newDate, rowKey, route) =>
  editExpiryDate(newDate, rowKey, 'TransactionBatch', route);

export const editStocktakeBatchExpiryDate = (newDate, rowKey, route) =>
  editExpiryDate(newDate, rowKey, 'StocktakeBatch', route);

/**
 * Edits the field `totalQuantity` of a rows underlying data object.
 *
 * @param {String|Number} value      The new value to set (parsed as a positive integer)
 * @param {String}        rowKey     The key of the row to edit.
 */
export const editTotalQuantity = (value, rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  if (objectToEdit) {
    UIDatabase.write(() => {
      objectToEdit.setTotalQuantity(UIDatabase, parsePositiveInteger(value));
    });

    dispatch(refreshRow(rowKey, route));
  }
};

/**
 * Edits a rows underlying `suggestedQuantity` field.
 *
 * @param {String|Number}   value       The new value to set as the required quantity.
 * @param {String}          rowKey      Key of the row to edit.
 * @param {String}          objectType  Type of object to edit i.e. 'RequisitionItem'
 */
export const editSuppliedQuantity = (value, rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  if (objectToEdit) {
    UIDatabase.write(() =>
      objectToEdit.setSuppliedQuantity(UIDatabase, parsePositiveInteger(value))
    );
  }

  dispatch(refreshRow(rowKey, route));
};

/**
 * Edits a rows underlying `requiredQuantity` field.
 *
 * @param {String|Number}   value       The new value to set as the required quantity.
 * @param {String}          rowKey      Key of the row to edit.
 * @param {String}          objectType  Type of object to edit i.e. 'RequisitionItem'
 */
export const editRequiredQuantity = (value, rowKey, objectType, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  if (objectToEdit) {
    UIDatabase.write(() => {
      objectToEdit.requiredQuantity = parsePositiveInteger(value);
      UIDatabase.save(objectType, objectToEdit);
    });

    dispatch(refreshRow(rowKey, route));
  }
};

/**
 * Wrapper around editRequiredQuantity.
 */
export const editRequisitionItemRequiredQuantity = (value, rowKey, route) =>
  editRequiredQuantity(value, rowKey, 'RequisitionItem', route);

/**
 * Edits a rows underlying countedTotalQuantity field.
 *
 * @param {String|Number}   value   The new value to set as the ccounted total quantity.
 * @param {String}          rowKey  Key of the row to edit.
 */
export const editCountedQuantity = (value, rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  if (objectToEdit) {
    objectToEdit.setCountedTotalQuantity(UIDatabase, parsePositiveInteger(value));
  }

  dispatch(refreshRow(rowKey, route));
};

/**
 * Edits a StocktakeBatches underlying `countedTotalQuantity`
 *
 * @param {String|Number}   value  New value for the underlying `countedTotalQuantity` field
 * @param {String}          rowKey Key of the row to edit.
 */
export const editStocktakeBatchCountedQuantity = (value, rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  if (objectToEdit) {
    UIDatabase.write(() => {
      objectToEdit.countedTotalQuantity = parsePositiveInteger(value);
      UIDatabase.save('StocktakeBatch', UIDatabase);
    });
  }

  dispatch(refreshRow(rowKey, route));
};

/**
 * Removes a reason from a rows underlying data.
 *
 * @param {String} rowKey   Key for the row to edit.
 */
export const removeReason = (rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);

  if (objectToEdit) {
    objectToEdit.removeReason(UIDatabase);
  }

  dispatch(refreshRow(rowKey, route));
};

/**
 * Handles reason logic for a particular object (stocktakeBatch or
 * StocktakeItem) - if there is a difference (between snapshot and
 * countedTotalQuantity) - then a reason should be related to this
 * object. For negative adjustments, a negativeInventoryAdjustment
 * reason should be applied. If positive, a positiveInventoryAdjustment.
 * A correct reason
 * If there is already a reason, do nothing. If there is no
 * difference, but a reason has been previously applied, remove it.
 *
 * @param {String} rowKey Key of the row to enforce a reason on
 */
export const enforceReasonChoice = (rowKey, route) => (dispatch, getState) => {
  const { data, keyExtractor } = pageStateSelector(getState());

  const objectToEdit = data.find(row => keyExtractor(row) === rowKey);
  if (!objectToEdit) return null;

  const { difference } = objectToEdit;
  // If there's no difference, just remove the reason
  if (!difference) return dispatch(removeReason(rowKey, route));

  const { validateReason } = objectToEdit;
  if (!validateReason) {
    return dispatch(openModal(MODAL_KEYS.ENFORCE_STOCKTAKE_REASON, rowKey, route));
  }
  return null;
};

/**
 * Applys a passed reason to the underlying row data. Can be a StocktakeItem
 * or StocktakeBatch.
 *
 * @param {Realm.Option} value Reason to apply to the underlying rorw.
 */
export const applyReason = (value, route) => (dispatch, getState) => {
  const { modalValue, keyExtractor } = pageStateSelector(getState());

  modalValue.applyReason(UIDatabase, value);

  const rowKey = keyExtractor(modalValue);

  dispatch(closeModal(route));
  dispatch(refreshRow(rowKey, route));
};

/**
 * Wrapper around `editCountedTotalQuantity`, splitting the action to enforce a
 * reason also.
 *
 * @param {String|Number}   value  New value for the underlying `countedTotalQuantity` field
 * @param {String}          rowKey Key of the row to edit.
 */
export const editCountedQuantityWithReason = (value, rowKey, route) => dispatch => {
  dispatch(editCountedQuantity(value, rowKey, route));
  dispatch(enforceReasonChoice(rowKey, route));
};

/**
 * Wrapper around `editStocktakeBatchCountedQuantity`, splitting the action to enforce a
 * reason also.
 *
 * @param {String|Number}   value  New value for the underlying `countedTotalQuantity` field
 * @param {String}          rowKey Key of the row to edit.
 */
export const editStocktakeBatchCountedQuantityWithReason = (value, rowKey, route) => dispatch => {
  dispatch(editStocktakeBatchCountedQuantity(value, rowKey, route));
  dispatch(enforceReasonChoice(rowKey, route));
};

export const CellActionsLookup = {
  refreshRow,
  refreshIndicatorRow,
  editExpiryDate,
  editTransactionBatchExpiryDate,
  editStocktakeBatchExpiryDate,
  editTotalQuantity,
  editSuppliedQuantity,
  editRequiredQuantity,
  editRequisitionItemRequiredQuantity,
  editCountedQuantity,
  editStocktakeBatchCountedQuantity,
  removeReason,
  enforceReasonChoice,
  applyReason,
  editBatchName,
  editIndicatorValue,
  editStocktakeBatchName,
  editCountedQuantityWithReason,
  editStocktakeBatchCountedQuantityWithReason,
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  name: 'MemorableTrait',
  package: 'foam.memento',
  properties: [
    {
      name: 'memento',
      hidden: true
    }
  ],

  methods: {
    init: function(args) {
      this.SUPER(args);

      for ( var i = 0, prop ; prop = this.model_.properties_[i] ; i++ ) {
        if ( ! prop.memorable ) continue;
        this.addPropertyListener(prop.name, this.updateMemento);
        if ( this[prop.name] != null && this[prop.name].model_ && this[prop.name].memento ) {
          this[prop.name].addPropertyListener('memento', this.updateMemento);
        }
      }
      this.addPropertyListener('memento', this.updateFromMemento);

      if ( this.hasOwnProperty('memento') ) {
        this.updateFromMemento(null, null, null, this.memento);
      } else {
        this.updateMemento();
      }
    },
    toMemento_: function() {
      var memento = {};

      for ( var i = 0, prop ; prop = this.model_.properties[i] ; i ++ ) {
        if ( ! prop.memorable ) continue;
        if ( this[prop.name] != null ) {
          memento[prop.name] = this[prop.name].memento || this[prop.name].toString();
        }
      }
      return memento;
    }
  },

  listeners: [
    {
      name: 'updateFromMemento',
      code: function(src, topic, old, memento) {
        for ( var key in memento ) {
          if ( this[key] && this[key].memento ) {
            this[key].memento = memento[key];
          } else {
            this[key] = memento[key]
          }
        }
      }
    },
    {
      name: 'updateMemento',
      code: function(src, topic, old, nu) {
        // If the new property value is a memorable modelled object
        // then we need to subscribe for changes to that object.
        if ( src === this && old && old.model_ && old.memento ) {
          old.removePropertyListener('memento', this.updateMemento);
        }

        if ( src === this && nu && nu.model_ && nu.memento ) {
          nu.addPropertyListener('memento', this.updateMemento);
        }

        this.memento = this.toMemento_();
      }
    }
  ]
});
<commit_msg>MemorableTrait: Add optional toMemento() property-property<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

CLASS({
  name: 'MemorableTrait',
  package: 'foam.memento',
  properties: [
    {
      name: 'memento',
      hidden: true
    }
  ],

  methods: {
    init: function(args) {
      this.SUPER(args);

      for ( var i = 0, prop ; prop = this.model_.properties_[i] ; i++ ) {
        if ( ! prop.memorable ) continue;
        this.addPropertyListener(prop.name, this.updateMemento);
        if ( this[prop.name] != null && this[prop.name].model_ && this[prop.name].memento ) {
          this[prop.name].addPropertyListener('memento', this.updateMemento);
        }
      }
      this.addPropertyListener('memento', this.updateFromMemento);

      if ( this.hasOwnProperty('memento') ) {
        this.updateFromMemento(null, null, null, this.memento);
      } else {
        this.updateMemento();
      }
    },
    toMemento_: function() {
      var memento = {};

      for ( var i = 0, prop ; prop = this.model_.properties[i] ; i ++ ) {
        if ( ! prop.memorable ) continue;
        if ( this[prop.name] != null ) {
          var value = this[prop.name];
          memento[prop.name] = value.memento || (value.toMemento ?
              value.toMemento() : value.toString());
        }
      }
      return memento;
    }
  },

  listeners: [
    {
      name: 'updateFromMemento',
      code: function(src, topic, old, memento) {
        for ( var key in memento ) {
          if ( this[key] && this[key].memento ) {
            this[key].memento = memento[key];
          } else {
            this[key] = memento[key]
          }
        }
      }
    },
    {
      name: 'updateMemento',
      code: function(src, topic, old, nu) {
        // If the new property value is a memorable modelled object
        // then we need to subscribe for changes to that object.
        if ( src === this && old && old.model_ && old.memento ) {
          old.removePropertyListener('memento', this.updateMemento);
        }

        if ( src === this && nu && nu.model_ && nu.memento ) {
          nu.addPropertyListener('memento', this.updateMemento);
        }

        this.memento = this.toMemento_();
      }
    }
  ]
});
<|endoftext|>