<commit_before>/*
 * Cross-domain task proxy implementation for Bebras task API - v1.0 - 08/2014
 *
 * This file implements a "TaskProxyManager" object in the global scope so that
 * platforms using this file can just call
 * "myTask = TaskProxyManager.getTaskProxy(task_id)" to get the task object
 * associated to a task loaded in an iframe. Then they can just call
 * "myTask.myFunction()", with function of the API, the task object will take
 * care of message sending and receiving with the actual task.
 * 'task_id' here refers to the "id" attribute of the iframe in which a task is
 * loaded.
 *
 * It depends on jQuery (though this could be removed) and jschannel.
 *
 */

/**
 * Global objects
 */

window.TaskProxyManager = {
   tasks: {},
   platforms: {},
   getRandomID: function() {
      var low = Math.floor(Math.random() * 922337203).toString();
      var high = Math.floor(Math.random() * 2000000000).toString();
      return high + low;
   },
   getTaskProxy: function(idFrame, success, force, error) {
      var errorFun = error ? error : function() {console.error(arguments);}
      if (TaskProxyManager.tasks[idFrame] && !force) {
         success(TaskProxyManager.tasks[idFrame]);
      } else {
         if (force) {
            TaskProxyManager.deleteTaskProxy(idFrame);
         }
         $('#'+idFrame).each(function() {
            TaskProxyManager.tasks[idFrame] = new Task($(this), function() {
               if (idFrame in TaskProxyManager.platforms) {
                  TaskProxyManager.tasks[idFrame].setPlatform(TaskProxyManager.platforms[idFrame]);
               }
               success(TaskProxyManager.tasks[idFrame]);
            }, errorFun);
         });
      }
   },
   setPlatform: function(task, platform) {
      TaskProxyManager.platforms[task.Id] = platform;
      TaskProxyManager.tasks[task.Id].setPlatform(platform);
   },
   deleteTaskProxy: function(idFrame) {
      var task = TaskProxyManager.tasks[idFrame];
      if (task && task.chan) {
         task.chan.destroy();
      }
      delete(TaskProxyManager.tasks[idFrame]);
      delete(TaskProxyManager.platforms[idFrame]);
   },
   getUrl: function(taskUrl, sToken, sPlatform, prefix, sLocale) {
      var channelId = (prefix ? prefix : '')+this.getRandomID();
      if (taskUrl.indexOf('?') == -1) {
         // the idea is not to change the base url even if we change token, so we put token after #
         taskUrl = taskUrl + '?';
      } else {
         taskUrl = taskUrl + '&';
      }
      var url = taskUrl+'sToken='+encodeURIComponent(sToken)+'&sPlatform='+encodeURIComponent(sPlatform)+'&channelId='+encodeURIComponent(channelId);
      if(sLocale) { url += '&sLocale='+encodeURIComponent(sLocale); }
      return url;
   }
};

TaskProxyManager.getGraderProxy = TaskProxyManager.getTaskProxy;

/**
 * Enable / Disable messages debug
 */
var platformDebug = false;

/*
 * Task object, created from an iframe DOM element
 */
function Task(iframe, success, error) {
   this.iframe = iframe;
   this.Id = iframe.attr('id');
   this.platformSet = false;
   function getUrlParameterByName(name, url) {
       var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
           results = regex.exec(url);
       return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
   }
   var nbSecs = 0;
   var checkInterval = setInterval(function() {
      if (nbSecs > 300) {
         error();
         clearInterval(checkInterval);
         checkInterval = null;
      }
      nbSecs = nbSecs + 1;
   }, 1000);
   this.chan = Channel.build({
      window: iframe[0].contentWindow,
      origin: "*",
      scope: getUrlParameterByName('channelId', iframe[0].src),
      onReady: function() {
         if (checkInterval) {
            clearInterval(checkInterval);
            success();
         }
      }
   });
   this.setPlatform = function(platform) {
      this.platform = platform;
      var self = this;
      if (this.platformSet) {
         // in this case, the bound functions will call the new platform
         return;
      }
      this.chan.bind('platform.validate', function (trans, mode) {
         self.platform.validate(mode, trans.complete, trans.error);
         trans.delayReturn(true);
         console.error('platform.validate with mode '+mode);
      });
      this.chan.bind('platform.getTaskParams', function (trans, keyDefault) {
         var key = keyDefault ? keyDefault[0] : undefined;
         var defaultValue = keyDefault ? keyDefault[1] : undefined;
         self.platform.getTaskParams(key, defaultValue, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      this.chan.bind('platform.showView', function (trans, view) {
         self.platform.showView(view, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      this.chan.bind('platform.askHint', function (trans, hintToken) {
         self.platform.askHint(hintToken, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      this.chan.bind('platform.updateDisplay', function (trans, data) {
         self.platform.updateDisplay(data, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      this.chan.bind('platform.openUrl', function (trans, url) {
         self.platform.openUrl(url, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      self.platformSet = true;

      // Legacy calls
      this.chan.bind('platform.updateHeight', function (trans, height) {
         self.platform.updateDisplay({ height: height }, trans.complete, trans.error);
         trans.delayReturn(true);
      });
   };
}

Task.prototype.getSourceId = function() {
   return this.Id;
};

Task.prototype.getTargetUrl = function() {
   return this.iframe.attr('src');
};

Task.prototype.getTarget = function() {
   return this.iframe[0].contentWindow;
};

Task.prototype.getDomain = function() {
   var url = this.getTargetUrl();
   return url.substr(0, url.indexOf('/', 7));
};

/**
 * Task API functions
 */
Task.prototype.load = function(views, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.load",
      params: views,
      timeout: 300000,
      success: success,
      error: error
   });
};

Task.prototype.unload = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.unload",
      timeout: 2000,
      error: error,
      success: success
   });
};

Task.prototype.getHeight = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getHeight",
      timeout: 500,
      error: error,
      success: success
   });
};

Task.prototype.updateToken = function(token, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.updateToken",
      params: token,
      timeout: 10000,
      error: error,
      success: success
   });
};

Task.prototype.getMetaData = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getMetaData",
      timeout: 2000,
      error: error,
      success: success
   });
};

Task.prototype.getAnswer = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getAnswer",
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.reloadAnswer = function(answer, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.reloadAnswer",
      params: answer,
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.getState = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getState",
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.reloadState = function(state, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.reloadState",
      params: state,
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.getViews = function(success, error) {
   if (!error) error = function() {console.error(arguments)};
   this.chan.call({method: "task.getViews",
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.showViews = function(views, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.showViews",
      params: views,
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.gradeAnswer = function(answer, answerToken, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   var newSuccess = function(successMonoArg) {
      if (successMonoArg instanceof Array) {
         var argzero = successMonoArg[0];
         var argone  = (successMonoArg.length > 1) ? successMonoArg[1] : null;
         var argtwo  = (successMonoArg.length > 2) ? successMonoArg[2] : null;
         var argthree  = (successMonoArg.length > 3) ? successMonoArg[3] : null;
         success(argzero, argone, argtwo, argthree);
      } else {
         success(successMonoArg);
      }
   };
   this.chan.call({method: "task.gradeAnswer",
      params: [answer, answerToken],
      error: error,
      success: newSuccess,
      timeout: window.gradeAnswerTimeoutVal ? window.gradeAnswerTimeoutVal : 40000
   });
};

Task.prototype.getResources = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getResources",
      params: [],
      error: error,
      success: success,
      timeout: 2000
   });
};

// for grader.gradeTask
Task.prototype.gradeTask = Task.prototype.gradeAnswer;

/*
 * Platform object definition, created from a Task object (see below)
 */

function Platform(task) {
   this.task = task;
}

Platform.prototype.getTask = function() {
   return this.task;
};

/*
 * Simple prototypes for platform API functions, to be overriden by your
 * platform's specific functions (for each platform object)
 */

Platform.prototype.validate = function(mode, success, error) {error('platform.validate is not defined');};
Platform.prototype.showView = function(views, success, error) {error('platform.validate is not defined');};
Platform.prototype.askHint = function(platformToken, success, error) {error('platform.validate is not defined');};
Platform.prototype.updateHeight = function(height, success, error) { this.updateDisplay({height: height}); };
Platform.prototype.updateDisplay = function(data, success, error) {
   if(data.height) {
      this.task.iframe.height(parseInt(height)+40);success();
   }
};
Platform.prototype.openUrl = function(url, success, error) {error('platform.openUrl is not defined!');};
Platform.prototype.getTaskParams = function(key, defaultValue, success, error) {
   var res = {minScore: -3, maxScore: 10, randomSeed: 0, noScore: 0, readOnly: false, options: {}};
   if (key) {
      if (key !== 'options' && key in res) {
         res = res[key];
      } else if (res.options && key in res.options) {
         res = res.options[key];
      } else {
         res = (typeof defaultValue !== 'undefined') ? defaultValue : null;
      }
   }
   success(res);
};

window.Platform = Platform;<commit_msg>Remove timeout on task.load<commit_after>/*
 * Cross-domain task proxy implementation for Bebras task API - v1.0 - 08/2014
 *
 * This file implements a "TaskProxyManager" object in the global scope so that
 * platforms using this file can just call
 * "myTask = TaskProxyManager.getTaskProxy(task_id)" to get the task object
 * associated to a task loaded in an iframe. Then they can just call
 * "myTask.myFunction()", with function of the API, the task object will take
 * care of message sending and receiving with the actual task.
 * 'task_id' here refers to the "id" attribute of the iframe in which a task is
 * loaded.
 *
 * It depends on jQuery (though this could be removed) and jschannel.
 *
 */

/**
 * Global objects
 */

window.TaskProxyManager = {
   tasks: {},
   platforms: {},
   getRandomID: function() {
      var low = Math.floor(Math.random() * 922337203).toString();
      var high = Math.floor(Math.random() * 2000000000).toString();
      return high + low;
   },
   getTaskProxy: function(idFrame, success, force, error) {
      var errorFun = error ? error : function() {console.error(arguments);}
      if (TaskProxyManager.tasks[idFrame] && !force) {
         success(TaskProxyManager.tasks[idFrame]);
      } else {
         if (force) {
            TaskProxyManager.deleteTaskProxy(idFrame);
         }
         $('#'+idFrame).each(function() {
            TaskProxyManager.tasks[idFrame] = new Task($(this), function() {
               if (idFrame in TaskProxyManager.platforms) {
                  TaskProxyManager.tasks[idFrame].setPlatform(TaskProxyManager.platforms[idFrame]);
               }
               success(TaskProxyManager.tasks[idFrame]);
            }, errorFun);
         });
      }
   },
   setPlatform: function(task, platform) {
      TaskProxyManager.platforms[task.Id] = platform;
      TaskProxyManager.tasks[task.Id].setPlatform(platform);
   },
   deleteTaskProxy: function(idFrame) {
      var task = TaskProxyManager.tasks[idFrame];
      if (task && task.chan) {
         task.chan.destroy();
      }
      delete(TaskProxyManager.tasks[idFrame]);
      delete(TaskProxyManager.platforms[idFrame]);
   },
   getUrl: function(taskUrl, sToken, sPlatform, prefix, sLocale) {
      var channelId = (prefix ? prefix : '')+this.getRandomID();
      if (taskUrl.indexOf('?') == -1) {
         // the idea is not to change the base url even if we change token, so we put token after #
         taskUrl = taskUrl + '?';
      } else {
         taskUrl = taskUrl + '&';
      }
      var url = taskUrl+'sToken='+encodeURIComponent(sToken)+'&sPlatform='+encodeURIComponent(sPlatform)+'&channelId='+encodeURIComponent(channelId);
      if(sLocale) { url += '&sLocale='+encodeURIComponent(sLocale); }
      return url;
   }
};

TaskProxyManager.getGraderProxy = TaskProxyManager.getTaskProxy;

/**
 * Enable / Disable messages debug
 */
var platformDebug = false;

/*
 * Task object, created from an iframe DOM element
 */
function Task(iframe, success, error) {
   this.iframe = iframe;
   this.Id = iframe.attr('id');
   this.platformSet = false;
   function getUrlParameterByName(name, url) {
       var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
           results = regex.exec(url);
       return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
   }
   var nbSecs = 0;
   var checkInterval = setInterval(function() {
      if (nbSecs > 300) {
         error();
         clearInterval(checkInterval);
         checkInterval = null;
      }
      nbSecs = nbSecs + 1;
   }, 1000);
   this.chan = Channel.build({
      window: iframe[0].contentWindow,
      origin: "*",
      scope: getUrlParameterByName('channelId', iframe[0].src),
      onReady: function() {
         if (checkInterval) {
            clearInterval(checkInterval);
            success();
         }
      }
   });
   this.setPlatform = function(platform) {
      this.platform = platform;
      var self = this;
      if (this.platformSet) {
         // in this case, the bound functions will call the new platform
         return;
      }
      this.chan.bind('platform.validate', function (trans, mode) {
         self.platform.validate(mode, trans.complete, trans.error);
         trans.delayReturn(true);
         console.error('platform.validate with mode '+mode);
      });
      this.chan.bind('platform.getTaskParams', function (trans, keyDefault) {
         var key = keyDefault ? keyDefault[0] : undefined;
         var defaultValue = keyDefault ? keyDefault[1] : undefined;
         self.platform.getTaskParams(key, defaultValue, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      this.chan.bind('platform.showView', function (trans, view) {
         self.platform.showView(view, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      this.chan.bind('platform.askHint', function (trans, hintToken) {
         self.platform.askHint(hintToken, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      this.chan.bind('platform.updateDisplay', function (trans, data) {
         self.platform.updateDisplay(data, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      this.chan.bind('platform.openUrl', function (trans, url) {
         self.platform.openUrl(url, trans.complete, trans.error);
         trans.delayReturn(true);
      });
      self.platformSet = true;

      // Legacy calls
      this.chan.bind('platform.updateHeight', function (trans, height) {
         self.platform.updateDisplay({ height: height }, trans.complete, trans.error);
         trans.delayReturn(true);
      });
   };
}

Task.prototype.getSourceId = function() {
   return this.Id;
};

Task.prototype.getTargetUrl = function() {
   return this.iframe.attr('src');
};

Task.prototype.getTarget = function() {
   return this.iframe[0].contentWindow;
};

Task.prototype.getDomain = function() {
   var url = this.getTargetUrl();
   return url.substr(0, url.indexOf('/', 7));
};

/**
 * Task API functions
 */
Task.prototype.load = function(views, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.load",
      params: views,
      success: success,
      error: error
   });
};

Task.prototype.unload = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.unload",
      timeout: 2000,
      error: error,
      success: success
   });
};

Task.prototype.getHeight = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getHeight",
      timeout: 500,
      error: error,
      success: success
   });
};

Task.prototype.updateToken = function(token, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.updateToken",
      params: token,
      timeout: 10000,
      error: error,
      success: success
   });
};

Task.prototype.getMetaData = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getMetaData",
      timeout: 2000,
      error: error,
      success: success
   });
};

Task.prototype.getAnswer = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getAnswer",
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.reloadAnswer = function(answer, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.reloadAnswer",
      params: answer,
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.getState = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getState",
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.reloadState = function(state, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.reloadState",
      params: state,
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.getViews = function(success, error) {
   if (!error) error = function() {console.error(arguments)};
   this.chan.call({method: "task.getViews",
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.showViews = function(views, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.showViews",
      params: views,
      error: error,
      success: success,
      timeout: 2000
   });
};

Task.prototype.gradeAnswer = function(answer, answerToken, success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   var newSuccess = function(successMonoArg) {
      if (successMonoArg instanceof Array) {
         var argzero = successMonoArg[0];
         var argone  = (successMonoArg.length > 1) ? successMonoArg[1] : null;
         var argtwo  = (successMonoArg.length > 2) ? successMonoArg[2] : null;
         var argthree  = (successMonoArg.length > 3) ? successMonoArg[3] : null;
         success(argzero, argone, argtwo, argthree);
      } else {
         success(successMonoArg);
      }
   };
   this.chan.call({method: "task.gradeAnswer",
      params: [answer, answerToken],
      error: error,
      success: newSuccess,
      timeout: window.gradeAnswerTimeoutVal ? window.gradeAnswerTimeoutVal : 40000
   });
};

Task.prototype.getResources = function(success, error) {
   if (!error) error = function(errMsg) {console.error(errMsg)};
   this.chan.call({method: "task.getResources",
      params: [],
      error: error,
      success: success,
      timeout: 2000
   });
};

// for grader.gradeTask
Task.prototype.gradeTask = Task.prototype.gradeAnswer;

/*
 * Platform object definition, created from a Task object (see below)
 */

function Platform(task) {
   this.task = task;
}

Platform.prototype.getTask = function() {
   return this.task;
};

/*
 * Simple prototypes for platform API functions, to be overriden by your
 * platform's specific functions (for each platform object)
 */

Platform.prototype.validate = function(mode, success, error) {error('platform.validate is not defined');};
Platform.prototype.showView = function(views, success, error) {error('platform.validate is not defined');};
Platform.prototype.askHint = function(platformToken, success, error) {error('platform.validate is not defined');};
Platform.prototype.updateHeight = function(height, success, error) { this.updateDisplay({height: height}); };
Platform.prototype.updateDisplay = function(data, success, error) {
   if(data.height) {
      this.task.iframe.height(parseInt(height)+40);success();
   }
};
Platform.prototype.openUrl = function(url, success, error) {error('platform.openUrl is not defined!');};
Platform.prototype.getTaskParams = function(key, defaultValue, success, error) {
   var res = {minScore: -3, maxScore: 10, randomSeed: 0, noScore: 0, readOnly: false, options: {}};
   if (key) {
      if (key !== 'options' && key in res) {
         res = res[key];
      } else if (res.options && key in res.options) {
         res = res.options[key];
      } else {
         res = (typeof defaultValue !== 'undefined') ? defaultValue : null;
      }
   }
   success(res);
};

window.Platform = Platform;
<|endoftext|>


<commit_before>/*
 * Title:              SciCalc - Scientific Calculator Module
 * Version:            0.1
 * Author:             Ryan Fung
 * Date created:	   2014-04-16
 * Date last modified: 2015-04-07
 */

var baseUnits = ['kg', 'm', 's', 'A', 'K', 'mol', 'cd'];
var unitData = {}; // global units dataset
var dimData = {}; // global Dimensions dataset

// Regular Expressions, keep search parameters
var re = {
	number: /([0-9])/g,
	letter: /([A-Za-z])/g,
	bracket: /([\(\)])/g,
	operator: /([\+\-\*\/\=])/g,
	nan: /([\+\-\*\/\=\(\)])/g
};
// Regular Expressions, discard search parameters
var red = {
	number: /[0-9]/g,
	letter: /[A-Za-z]/g,
	bracket: /[\(\)]/g,
	operator: /[\+\-\*\/\=]/g,
	nan: /[\+\-\*\/\=\(\)]/g
};
// Regular Expressions for operations
var reo = {
	1: /[\^]/g,
	2: /[*\/]/g,
	3: /[\+\-]/g
}

window.onload = function() {
	$('#scicalcSubmit').on('click', function () {
		checkInput($('#scicalcInput').val());
	});
	$('#scicalcForm').submit(function () {
		// parseInput();
		return false;
	});
	$('#scicalcInput').focus();
	writeMessage('scicalc.js successfully loaded!');
	for(i in siUnits){
		var a = 0;
		for (j in siUnits[i][1]) {
			a += j;
		}
		unitData[siUnits[i][0]] = siUnits[i][1];
	}
}

function parseInput(input){ // parse input expression
	var exp = input.split(re.nan);

	// convert numbers to object
	for (var i = 0; i < exp.length; i++) {
		if (!(re.nan.test(exp[i]))) {
			exp[i] = new dim(Number(exp[i]), 0);
		}
	}

	// perform multiplication
	for (var i = 0; i < exp.length; i++) {
		if (exp[i] == '*' && i > 0 && i < exp.length) {
			exp.splice(i-1, 3, multiply(exp[i-1], exp[i+1]));
			i--;
		}
	}

	// perform priority 3 operations
	for (var i = 1; i < exp.length - 1; i++) {
		console.log("exp[i].split(reo[1]).join('')  =  "+String(exp[i].split(reo[3]).join('')));
		if (exp[i].split(reo[3]).join('')=='') {
			// perform addition
			if (exp[i] == '+') {
				console.log('addition');
				exp.splice(i-1, 3, add(exp[i-1], exp[i+1]));
				i--;
			}
			// perform subtraction
			if (exp[i] == '-') {
				console.log('subtraction');
				exp.splice(i-1, 3, add(exp[i-1], exp[i+1], true));
				i--;
			}
		}
		console.log('i  =  '+i);
		console.log('exp[i]  =  '+exp[i]);
	}

	if(exp.length != 1) {
		raiseError('Not all operations performed on calculation');
		console.log(exp);
	} else {
		return exp[0];
	}
}

function checkInput (input) {
	if ( input.split(/\s/g).join('') !== '' ) {
		var result = parseInput(input);
		console.log(result);
		writeMessage(input + ' = ' + result);
	}
}

function multiply (num1, num2) {
	return new dim(
		num1.value * num2.value,
		[
			num1.units[0] + num2.units[0],
			num1.units[1] + num2.units[1],
			num1.units[2] + num2.units[2],
			num1.units[3] + num2.units[3],
			num1.units[4] + num2.units[4],
			num1.units[5] + num2.units[5],
			num1.units[6] + num2.units[6]
		]
	);
}

function add (num1, num2, subtract) {
	if (
		num1.units[0] == num2.units[0] &&
		num1.units[1] == num2.units[1] &&
		num1.units[2] == num2.units[2] &&
		num1.units[3] == num2.units[3] &&
		num1.units[4] == num2.units[4] &&
		num1.units[5] == num2.units[5] &&
		num1.units[6] == num2.units[6]
	) {
		if (!subtract) {
			return new dim(num1.value + num2.value, num1.units);
		} else {
			return new dim(num1.value - num2.value, num1.units);
		}
	} else {
		raiseError('Dimension mismatch');
	}
}

function dim(value, units) { // new Dimension
	this.value = value;
	if (units == 0) {
		this.units = [0,0,0,0,0,0,0];
	} else {
		this.units = units;
	}
	this.equal = function (arg) {
		if (arg instanceof dim) {
			return this.identical(arg);
		} else if (typeof(arg=='number')) {
			return this.value == arg
		}
	}
	this.identical = function (arg) {
		if (arg instanceof dim) {
			return this.identical(dim2);
			if (
				this.value == dim2.value &&
				this.units[0] == dim2.units[0] &&
				this.units[1] == dim2.units[1] &&
				this.units[2] == dim2.units[2] &&
				this.units[3] == dim2.units[3] &&
				this.units[4] == dim2.units[4] &&
				this.units[5] == dim2.units[5] &&
				this.units[6] == dim2.units[6]
				// && this.mag == dim2.mag
			) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
}
dim.prototype.toString = function () {return String(this.value)};

function makeNum(id, value, units) {
	dimData[id] = new dim(value,units);
}

function ungroup(s) { // extract expression from inside round brackets
	try {
		s = s.split('(')[1].split(')')[0];
	} catch (e) {
	}
	return s;
}

function extractVar(s) { // extract variable name from variable string

}

function writeMessage(msg) {
	element = document.createElement('p');
	element.innerHTML = msg;
	box = document.getElementById('scicalcOutput');
	if (box.children == 0) {
		box.appendChild(element);
	} else {
		box.insertBefore(element, box.firstChild);
	}
}

function raiseError(msg) {
	writeMessage('Error' + msg);
	console.log('Error' + msg);
}<commit_msg>Added division function<commit_after>/*
 * Title:              SciCalc - Scientific Calculator Module
 * Version:            0.1
 * Author:             Ryan Fung
 * Date created:	   2014-04-16
 * Date last modified: 2015-04-07
 */

var baseUnits = ['kg', 'm', 's', 'A', 'K', 'mol', 'cd'];
var unitData = {}; // global units dataset
var dimData = {}; // global Dimensions dataset

// Regular Expressions, keep search parameters
var re = {
	number: /([0-9])/g,
	letter: /([A-Za-z])/g,
	bracket: /([\(\)])/g,
	operator: /([\+\-\*\/\=])/g,
	nan: /([\+\-\*\/\=\(\)])/g
};
// Regular Expressions, discard search parameters
var red = {
	number: /[0-9]/g,
	letter: /[A-Za-z]/g,
	bracket: /[\(\)]/g,
	operator: /[\+\-\*\/\=]/g,
	nan: /[\+\-\*\/\=\(\)]/g
};
// Regular Expressions for operations
var reo = {
	1: /[\^]/g,
	2: /[*\/]/g,
	3: /[\+\-]/g
}

window.onload = function() {
	$('#scicalcSubmit').on('click', function () {
		checkInput($('#scicalcInput').val());
	});
	$('#scicalcForm').submit(function () {
		// parseInput();
		return false;
	});
	$('#scicalcInput').focus();
	writeMessage('scicalc.js successfully loaded!');
	for(i in siUnits){
		var a = 0;
		for (j in siUnits[i][1]) {
			a += j;
		}
		unitData[siUnits[i][0]] = siUnits[i][1];
	}
}

function parseInput(input){ // parse input expression
	var exp = input.split(re.nan);

	// convert numbers to object
	for (var i = 0; i < exp.length; i++) {
		if (!(re.nan.test(exp[i]))) {
			exp[i] = new dim(Number(exp[i]), 0);
		}
	}

	// perform priority 2 operations
	for (var i = 1; i < exp.length - 1; i++) {
		if (exp[i].split(reo[2]).join('')=='') {
			// perform multiplication
			if (exp[i] == '*') {
				console.log('multiplication');
				exp.splice(i-1, 3, multiply(exp[i-1], exp[i+1]));
				i--;
			}
			// perform division
			if (exp[i] == '/') {
				console.log('division');
				exp.splice(i-1, 3, multiply(exp[i-1], exp[i+1], true));
				i--;
			}
		}
	}

	// perform priority 3 operations
	for (var i = 1; i < exp.length - 1; i++) {
		console.log("exp[i].split(reo[1]).join('')  =  "+String(exp[i].split(reo[3]).join('')));
		if (exp[i].split(reo[3]).join('')=='') {
			// perform addition
			if (exp[i] == '+') {
				console.log('addition');
				exp.splice(i-1, 3, add(exp[i-1], exp[i+1]));
				i--;
			}
			// perform subtraction
			if (exp[i] == '-') {
				console.log('subtraction');
				exp.splice(i-1, 3, add(exp[i-1], exp[i+1], true));
				i--;
			}
		}
		console.log('i  =  '+i);
		console.log('exp[i]  =  '+exp[i]);
	}

	if(exp.length != 1) {
		raiseError('Not all operations performed on calculation');
		console.log(exp);
	} else {
		return exp[0];
	}
}

function checkInput (input) {
	if ( input.split(/\s/g).join('') !== '' ) {
		var result = parseInput(input);
		console.log(result);
		writeMessage(input + ' = ' + result);
	}
}

function multiply (num1, num2, divide) {
	if (!divide) {
		return new dim(
			num1.value * num2.value,
			[
				num1.units[0] + num2.units[0],
				num1.units[1] + num2.units[1],
				num1.units[2] + num2.units[2],
				num1.units[3] + num2.units[3],
				num1.units[4] + num2.units[4],
				num1.units[5] + num2.units[5],
				num1.units[6] + num2.units[6]
			]
		);
	} else {
		return new dim(
			num1.value / num2.value,
			[
				num1.units[0] - num2.units[0],
				num1.units[1] - num2.units[1],
				num1.units[2] - num2.units[2],
				num1.units[3] - num2.units[3],
				num1.units[4] - num2.units[4],
				num1.units[5] - num2.units[5],
				num1.units[6] - num2.units[6]
			]
		);
	}
}

function add (num1, num2, subtract) {
	if (
		num1.units[0] == num2.units[0] &&
		num1.units[1] == num2.units[1] &&
		num1.units[2] == num2.units[2] &&
		num1.units[3] == num2.units[3] &&
		num1.units[4] == num2.units[4] &&
		num1.units[5] == num2.units[5] &&
		num1.units[6] == num2.units[6]
	) {
		if (!subtract) {
			return new dim(num1.value + num2.value, num1.units);
		} else {
			return new dim(num1.value - num2.value, num1.units);
		}
	} else {
		raiseError('Dimension mismatch');
	}
}

function dim(value, units) { // new Dimension
	this.value = value;
	if (units == 0) {
		this.units = [0,0,0,0,0,0,0];
	} else {
		this.units = units;
	}
	this.equal = function (arg) {
		if (arg instanceof dim) {
			return this.identical(arg);
		} else if (typeof(arg=='number')) {
			return this.value == arg
		}
	}
	this.identical = function (arg) {
		if (arg instanceof dim) {
			return this.identical(dim2);
			if (
				this.value == dim2.value &&
				this.units[0] == dim2.units[0] &&
				this.units[1] == dim2.units[1] &&
				this.units[2] == dim2.units[2] &&
				this.units[3] == dim2.units[3] &&
				this.units[4] == dim2.units[4] &&
				this.units[5] == dim2.units[5] &&
				this.units[6] == dim2.units[6]
				// && this.mag == dim2.mag
			) {
				return true;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
}
dim.prototype.toString = function () {return String(this.value)};

function makeNum(id, value, units) {
	dimData[id] = new dim(value,units);
}

function ungroup(s) { // extract expression from inside round brackets
	try {
		s = s.split('(')[1].split(')')[0];
	} catch (e) {
	}
	return s;
}

function extractVar(s) { // extract variable name from variable string

}

function writeMessage(msg) {
	element = document.createElement('p');
	element.innerHTML = msg;
	box = document.getElementById('scicalcOutput');
	if (box.children == 0) {
		box.appendChild(element);
	} else {
		box.insertBefore(element, box.firstChild);
	}
}

function raiseError(msg) {
	writeMessage('Error' + msg);
	console.log('Error' + msg);
}<|endoftext|>


<commit_before>import {
    geoExtent, geoLineIntersection, geoMetersToLat, geoMetersToLon,
    geoSphericalDistance, geoVecInterp, geoHasSelfIntersections,
    geoSphericalClosestNode
} from '../geo';

import { actionAddMidpoint } from '../actions/add_midpoint';
import { actionChangeTags } from '../actions/change_tags';
import { actionMergeNodes } from '../actions/merge_nodes';
import { t } from '../util/locale';
import { utilDisplayLabel } from '../util';
import { osmRoutableHighwayTagValues } from '../osm/tags';
import { validationIssue, validationIssueFix } from '../core/validation';
import { services } from '../services';

/**
 * Look for roads that can be connected to other roads with a short extension
 */
export function validationAlmostJunction(context) {
    var type = 'almost_junction';

    function isHighway(entity) {
        return entity.type === 'way' &&
            osmRoutableHighwayTagValues[entity.tags.highway];
    }

    function isTaggedAsNotContinuing(node) {
        return node.tags.noexit === 'yes' ||
            node.tags.amenity === 'parking_entrance' ||
            (node.tags.entrance && node.tags.entrance !== 'no');
    }

    var validation = function checkAlmostJunction(entity, graph) {
        if (!isHighway(entity)) return [];
        if (entity.isDegenerate()) return [];

        var tree = context.history().tree();
        var issues = [];

        var extendableNodeInfos = findConnectableEndNodesByExtension(entity);
        extendableNodeInfos.forEach(function(extendableNodeInfo) {
            issues.push(new validationIssue({
                type: type,
                subtype: 'highway-highway',
                severity: 'warning',
                message: function(context) {
                    var entity1 = context.hasEntity(this.entityIds[0]);
                    if (this.entityIds[0] === this.entityIds[2]) {
                        return entity1 ? t('issues.almost_junction.self.message', {
                            feature: utilDisplayLabel(entity1, context)
                        }) : '';
                    } else {
                        var entity2 = context.hasEntity(this.entityIds[2]);
                        return (entity1 && entity2) ? t('issues.almost_junction.message', {
                            feature: utilDisplayLabel(entity1, context),
                            feature2: utilDisplayLabel(entity2, context)
                        }) : '';
                    }
                },
                reference: showReference,
                entityIds: [entity.id, extendableNodeInfo.node.id, extendableNodeInfo.wid],
                loc: extendableNodeInfo.node.loc,
                hash: JSON.stringify(extendableNodeInfo.node.loc),
                data: {
                    edge: extendableNodeInfo.edge,
                    cross_loc: extendableNodeInfo.cross_loc
                },
                dynamicFixes: makeFixes
            }));
        });

        return issues;

        function makeFixes(context) {
            var fixes = [new validationIssueFix({
                icon: 'iD-icon-abutment',
                title: t('issues.fix.connect_features.title'),
                onClick: function(context) {
                    var endNodeId = this.issue.entityIds[1];
                    var endNode = context.entity(endNodeId);
                    var targetEdge = this.issue.data.edge;
                    var crossLoc = this.issue.data.cross_loc;
                    var edgeNodes = [context.entity(targetEdge[0]), context.entity(targetEdge[1])];
                    var closestNodeInfo = geoSphericalClosestNode(edgeNodes, crossLoc);

                    var annotation = t('issues.fix.connect_almost_junction.annotation');
                    // already a point nearby, just connect to that
                    if (closestNodeInfo.distance < 0.75) {
                        context.perform(
                            actionMergeNodes([closestNodeInfo.node.id, endNode.id], closestNodeInfo.node.loc),
                            annotation
                        );
                    // else add the end node to the edge way
                    } else {
                        context.perform(
                            actionAddMidpoint({loc: crossLoc, edge: targetEdge}, endNode),
                            annotation
                        );
                    }
                }
            })];

            var node = context.hasEntity(this.entityIds[1]);
            if (node && Object.keys(node.tags).length === 0) {
                // node has no tags, suggest noexit fix
                fixes.push(new validationIssueFix({
                    icon: 'maki-barrier',
                    title: t('issues.fix.tag_as_disconnected.title'),
                    onClick: function(context) {
                        var nodeID = this.issue.entityIds[1];
                        context.perform(
                            actionChangeTags(nodeID, { noexit: 'yes' }),
                            t('issues.fix.tag_as_disconnected.annotation')
                        );
                    }
                }));
            }

            return fixes;
        }

        function showReference(selection) {
            selection.selectAll('.issue-reference')
                .data([0])
                .enter()
                .append('div')
                .attr('class', 'issue-reference')
                .text(t('issues.almost_junction.highway-highway.reference'));
        }

        function isExtendableCandidate(node, way) {
            // can not accurately test vertices on tiles not downloaded from osm - #5938
            var osm = services.osm;
            if (osm && !osm.isDataLoaded(node.loc)) {
                return false;
            }
            if (isTaggedAsNotContinuing(node) || graph.parentWays(node).length !== 1) {
                return false;
            }

            var occurences = 0;
            for (var index in way.nodes) {
                if (way.nodes[index] === node.id) {
                    occurences += 1;
                    if (occurences > 1) {
                        return false;
                    }
                }
            }
            return true;
        }

        function findConnectableEndNodesByExtension(way) {
            var results = [];
            if (way.isClosed()) return results;

            var testNodes;
            var indices = [0, way.nodes.length - 1];
            indices.forEach(function(nodeIndex) {
                var nodeID = way.nodes[nodeIndex];
                var node = graph.entity(nodeID);

                if (!isExtendableCandidate(node, way)) return;

                var connectionInfo = canConnectByExtend(way, nodeIndex);
                if (!connectionInfo) return;

                testNodes = graph.childNodes(way).slice();   // shallow copy
                testNodes[nodeIndex] = testNodes[nodeIndex].move(connectionInfo.cross_loc);

                // don't flag issue if connecting the ways would cause self-intersection
                if (geoHasSelfIntersections(testNodes, nodeID)) return;

                results.push(connectionInfo);
            });

            return results;
        }

        function hasTag(tags, key) {
            return tags[key] !== undefined && tags[key] !== 'no';
        }

        function canConnectWays(way, way2) {

            // allow self-connections
            if (way.id === way2.id) return true;

            // if one is bridge or tunnel, both must be bridge or tunnel
            if ((hasTag(way.tags, 'bridge') || hasTag(way2.tags, 'bridge')) &&
                !(hasTag(way.tags, 'bridge') && hasTag(way2.tags, 'bridge'))) return false;
            if ((hasTag(way.tags, 'tunnel') || hasTag(way2.tags, 'tunnel')) &&
                !(hasTag(way.tags, 'tunnel') && hasTag(way2.tags, 'tunnel'))) return false;

            // must have equivalent layers and levels
            var layer1 = way.tags.layer || '0',
                layer2 = way2.tags.layer || '0';
            if (layer1 !== layer2) return false;

            var level1 = way.tags.level || '0',
                level2 = way2.tags.level || '0';
            if (level1 !== level2) return false;

            return true;
        }

        function canConnectByExtend(way, endNodeIdx) {
            var EXTEND_TH_METERS = 5;
            var tipNid = way.nodes[endNodeIdx];  // the 'tip' node for extension point
            var midNid = endNodeIdx === 0 ? way.nodes[1] : way.nodes[way.nodes.length - 2];  // the other node of the edge
            var tipNode = graph.entity(tipNid);
            var midNode = graph.entity(midNid);
            var lon = tipNode.loc[0];
            var lat = tipNode.loc[1];
            var lon_range = geoMetersToLon(EXTEND_TH_METERS, lat) / 2;
            var lat_range = geoMetersToLat(EXTEND_TH_METERS) / 2;
            var queryExtent = geoExtent([
                [lon - lon_range, lat - lat_range],
                [lon + lon_range, lat + lat_range]
            ]);

            // first, extend the edge of [midNode -> tipNode] by EXTEND_TH_METERS and find the "extended tip" location
            var edgeLen = geoSphericalDistance(midNode.loc, tipNode.loc);
            var t = EXTEND_TH_METERS / edgeLen + 1.0;
            var extTipLoc = geoVecInterp(midNode.loc, tipNode.loc, t);

            // then, check if the extension part [tipNode.loc -> extTipLoc] intersects any other ways
            var intersected = tree.intersects(queryExtent, graph);
            for (var i = 0; i < intersected.length; i++) {
                var way2 = intersected[i];

                if (!isHighway(way2)) continue;

                if (!canConnectWays(way, way2)) continue;

                for (var j = 0; j < way2.nodes.length - 1; j++) {
                    var nAid = way2.nodes[j],
                        nBid = way2.nodes[j + 1];

                    if (nAid === tipNid || nBid === tipNid) continue;

                    var nA = graph.entity(nAid),
                        nB = graph.entity(nBid);
                    var crossLoc = geoLineIntersection([tipNode.loc, extTipLoc], [nA.loc, nB.loc]);
                    if (crossLoc) {
                        return {
                            node: tipNode,
                            wid: way2.id,
                            edge: [nA.id, nB.id],
                            cross_loc: crossLoc
                        };
                    }
                }
            }
            return null;
        }
    };

    validation.type = type;

    return validation;
}
<commit_msg>Suggest noexit fix for almost junctions even if the endpoint has uninteresting tags<commit_after>import {
    geoExtent, geoLineIntersection, geoMetersToLat, geoMetersToLon,
    geoSphericalDistance, geoVecInterp, geoHasSelfIntersections,
    geoSphericalClosestNode
} from '../geo';

import { actionAddMidpoint } from '../actions/add_midpoint';
import { actionChangeTags } from '../actions/change_tags';
import { actionMergeNodes } from '../actions/merge_nodes';
import { t } from '../util/locale';
import { utilDisplayLabel } from '../util';
import { osmRoutableHighwayTagValues } from '../osm/tags';
import { validationIssue, validationIssueFix } from '../core/validation';
import { services } from '../services';

/**
 * Look for roads that can be connected to other roads with a short extension
 */
export function validationAlmostJunction(context) {
    var type = 'almost_junction';

    function isHighway(entity) {
        return entity.type === 'way' &&
            osmRoutableHighwayTagValues[entity.tags.highway];
    }

    function isTaggedAsNotContinuing(node) {
        return node.tags.noexit === 'yes' ||
            node.tags.amenity === 'parking_entrance' ||
            (node.tags.entrance && node.tags.entrance !== 'no');
    }

    var validation = function checkAlmostJunction(entity, graph) {
        if (!isHighway(entity)) return [];
        if (entity.isDegenerate()) return [];

        var tree = context.history().tree();
        var issues = [];

        var extendableNodeInfos = findConnectableEndNodesByExtension(entity);
        extendableNodeInfos.forEach(function(extendableNodeInfo) {
            issues.push(new validationIssue({
                type: type,
                subtype: 'highway-highway',
                severity: 'warning',
                message: function(context) {
                    var entity1 = context.hasEntity(this.entityIds[0]);
                    if (this.entityIds[0] === this.entityIds[2]) {
                        return entity1 ? t('issues.almost_junction.self.message', {
                            feature: utilDisplayLabel(entity1, context)
                        }) : '';
                    } else {
                        var entity2 = context.hasEntity(this.entityIds[2]);
                        return (entity1 && entity2) ? t('issues.almost_junction.message', {
                            feature: utilDisplayLabel(entity1, context),
                            feature2: utilDisplayLabel(entity2, context)
                        }) : '';
                    }
                },
                reference: showReference,
                entityIds: [entity.id, extendableNodeInfo.node.id, extendableNodeInfo.wid],
                loc: extendableNodeInfo.node.loc,
                hash: JSON.stringify(extendableNodeInfo.node.loc),
                data: {
                    edge: extendableNodeInfo.edge,
                    cross_loc: extendableNodeInfo.cross_loc
                },
                dynamicFixes: makeFixes
            }));
        });

        return issues;

        function makeFixes(context) {
            var fixes = [new validationIssueFix({
                icon: 'iD-icon-abutment',
                title: t('issues.fix.connect_features.title'),
                onClick: function(context) {
                    var endNodeId = this.issue.entityIds[1];
                    var endNode = context.entity(endNodeId);
                    var targetEdge = this.issue.data.edge;
                    var crossLoc = this.issue.data.cross_loc;
                    var edgeNodes = [context.entity(targetEdge[0]), context.entity(targetEdge[1])];
                    var closestNodeInfo = geoSphericalClosestNode(edgeNodes, crossLoc);

                    var annotation = t('issues.fix.connect_almost_junction.annotation');
                    // already a point nearby, just connect to that
                    if (closestNodeInfo.distance < 0.75) {
                        context.perform(
                            actionMergeNodes([closestNodeInfo.node.id, endNode.id], closestNodeInfo.node.loc),
                            annotation
                        );
                    // else add the end node to the edge way
                    } else {
                        context.perform(
                            actionAddMidpoint({loc: crossLoc, edge: targetEdge}, endNode),
                            annotation
                        );
                    }
                }
            })];

            var node = context.hasEntity(this.entityIds[1]);
            if (node && !node.hasInterestingTags()) {
                // node has no descriptive tags, suggest noexit fix
                fixes.push(new validationIssueFix({
                    icon: 'maki-barrier',
                    title: t('issues.fix.tag_as_disconnected.title'),
                    onClick: function(context) {
                        var nodeID = this.issue.entityIds[1];
                        var tags = Object.assign({}, context.entity(nodeID).tags);
                        tags.noexit = 'yes';
                        context.perform(
                            actionChangeTags(nodeID, tags),
                            t('issues.fix.tag_as_disconnected.annotation')
                        );
                    }
                }));
            }

            return fixes;
        }

        function showReference(selection) {
            selection.selectAll('.issue-reference')
                .data([0])
                .enter()
                .append('div')
                .attr('class', 'issue-reference')
                .text(t('issues.almost_junction.highway-highway.reference'));
        }

        function isExtendableCandidate(node, way) {
            // can not accurately test vertices on tiles not downloaded from osm - #5938
            var osm = services.osm;
            if (osm && !osm.isDataLoaded(node.loc)) {
                return false;
            }
            if (isTaggedAsNotContinuing(node) || graph.parentWays(node).length !== 1) {
                return false;
            }

            var occurences = 0;
            for (var index in way.nodes) {
                if (way.nodes[index] === node.id) {
                    occurences += 1;
                    if (occurences > 1) {
                        return false;
                    }
                }
            }
            return true;
        }

        function findConnectableEndNodesByExtension(way) {
            var results = [];
            if (way.isClosed()) return results;

            var testNodes;
            var indices = [0, way.nodes.length - 1];
            indices.forEach(function(nodeIndex) {
                var nodeID = way.nodes[nodeIndex];
                var node = graph.entity(nodeID);

                if (!isExtendableCandidate(node, way)) return;

                var connectionInfo = canConnectByExtend(way, nodeIndex);
                if (!connectionInfo) return;

                testNodes = graph.childNodes(way).slice();   // shallow copy
                testNodes[nodeIndex] = testNodes[nodeIndex].move(connectionInfo.cross_loc);

                // don't flag issue if connecting the ways would cause self-intersection
                if (geoHasSelfIntersections(testNodes, nodeID)) return;

                results.push(connectionInfo);
            });

            return results;
        }

        function hasTag(tags, key) {
            return tags[key] !== undefined && tags[key] !== 'no';
        }

        function canConnectWays(way, way2) {

            // allow self-connections
            if (way.id === way2.id) return true;

            // if one is bridge or tunnel, both must be bridge or tunnel
            if ((hasTag(way.tags, 'bridge') || hasTag(way2.tags, 'bridge')) &&
                !(hasTag(way.tags, 'bridge') && hasTag(way2.tags, 'bridge'))) return false;
            if ((hasTag(way.tags, 'tunnel') || hasTag(way2.tags, 'tunnel')) &&
                !(hasTag(way.tags, 'tunnel') && hasTag(way2.tags, 'tunnel'))) return false;

            // must have equivalent layers and levels
            var layer1 = way.tags.layer || '0',
                layer2 = way2.tags.layer || '0';
            if (layer1 !== layer2) return false;

            var level1 = way.tags.level || '0',
                level2 = way2.tags.level || '0';
            if (level1 !== level2) return false;

            return true;
        }

        function canConnectByExtend(way, endNodeIdx) {
            var EXTEND_TH_METERS = 5;
            var tipNid = way.nodes[endNodeIdx];  // the 'tip' node for extension point
            var midNid = endNodeIdx === 0 ? way.nodes[1] : way.nodes[way.nodes.length - 2];  // the other node of the edge
            var tipNode = graph.entity(tipNid);
            var midNode = graph.entity(midNid);
            var lon = tipNode.loc[0];
            var lat = tipNode.loc[1];
            var lon_range = geoMetersToLon(EXTEND_TH_METERS, lat) / 2;
            var lat_range = geoMetersToLat(EXTEND_TH_METERS) / 2;
            var queryExtent = geoExtent([
                [lon - lon_range, lat - lat_range],
                [lon + lon_range, lat + lat_range]
            ]);

            // first, extend the edge of [midNode -> tipNode] by EXTEND_TH_METERS and find the "extended tip" location
            var edgeLen = geoSphericalDistance(midNode.loc, tipNode.loc);
            var t = EXTEND_TH_METERS / edgeLen + 1.0;
            var extTipLoc = geoVecInterp(midNode.loc, tipNode.loc, t);

            // then, check if the extension part [tipNode.loc -> extTipLoc] intersects any other ways
            var intersected = tree.intersects(queryExtent, graph);
            for (var i = 0; i < intersected.length; i++) {
                var way2 = intersected[i];

                if (!isHighway(way2)) continue;

                if (!canConnectWays(way, way2)) continue;

                for (var j = 0; j < way2.nodes.length - 1; j++) {
                    var nAid = way2.nodes[j],
                        nBid = way2.nodes[j + 1];

                    if (nAid === tipNid || nBid === tipNid) continue;

                    var nA = graph.entity(nAid),
                        nB = graph.entity(nBid);
                    var crossLoc = geoLineIntersection([tipNode.loc, extTipLoc], [nA.loc, nB.loc]);
                    if (crossLoc) {
                        return {
                            node: tipNode,
                            wid: way2.id,
                            edge: [nA.id, nB.id],
                            cross_loc: crossLoc
                        };
                    }
                }
            }
            return null;
        }
    };

    validation.type = type;

    return validation;
}
<|endoftext|>


<commit_before>/*!
 * Original idea: https://github.com/Nycto/PicoModal
 * Accessibility inspiration: https://github.com/nico3333fr/jquery-accessible-modal-window-aria
 * MIT License (https://github.com/Aymkdn/picoModal-Accessible/blob/master/LICENSE)
*/
!function(name, definition) {
  if (typeof module != 'undefined') module.exports = definition()
  else if (typeof define == 'function' && typeof define.amd == 'object') define(definition)
  else this[name] = definition()
}('picoModal', function() {

  var picoModal = (function(window, document) {
    "use strict";

    // Generates observable objects that can be watched and triggered
    var observable = function() {
      var callbacks = [];
      return {
        watch: function(callback) {
          callbacks.push(callback);
        },
        trigger: function() {
          for (var i = 0; i < callbacks.length; i++) {
            window.setTimeout(callbacks[i], 1);
          }
        }
      };
    };

    // bind events
    var bindEvent = function(element, eventName, fct) {
      if (element.addEventListener) element.addEventListener(eventName, fct, false);
      else element.attachEvent("on"+eventName, fct);
    };

    // get window size
    // @return {width,hegiht}
    // source: http://help.dottoro.com/ljmclkbi.php
    var getWindowSize = function() {
      // always return 1, except at non-default zoom levels in IE before version 8
      var GetZoomFactor=function() {
        var factor = 1;
        if (document.body.getBoundingClientRect) {
          // rect is only in physical pixel size in IE before version 8
          var rect = document.body.getBoundingClientRect();
          var physicalW = rect.right - rect.left;
          var logicalW = document.body.offsetWidth;

          // the zoom level is always an integer percent value
          factor = Math.round ((physicalW / logicalW) * 100) / 100;
        }
        return factor;
      };
      var zoomFactor = GetZoomFactor();
      var w = Math.round (document.documentElement.clientWidth / zoomFactor);
      var h = Math.round (document.documentElement.clientHeight / zoomFactor);
      return {width:w, height:h};
    };

    // A small interface for creating and managing a dom element
    var make = function(parent, tag) {
      if (arguments.length == 1 && typeof parent === "string") {
        tag = parent;
        parent = null;
      }
      tag = tag || 'div';

      var elem = document.createElement(tag);
      (parent || document.body).appendChild(elem);

      var iface = {

        elem: elem,

        // Creates a child of this node
        child: function(tag) {
          return make(elem, tag || 'a');
        },

        // Applies a set of styles to an element
        stylize: function(styles) {
          styles = styles || {};

          if (typeof styles.opacity !== "undefined") {
            styles.filter =
              "alpha(opacity=" + (styles.opacity * 100) + ")";
          }

          for (var prop in styles) {
            if (styles.hasOwnProperty(prop)) {
              elem.style[prop] = styles[prop];
            }
          }

          return iface;
        },

        // Adds a class name
        clazz: function(clazz) {
          elem.className += clazz;
          return iface;
        },
        // set attributes
        setAttr: function(attr) {
          for (var a in attr) {
            if (attr.hasOwnProperty(a)) {
              elem.setAttribute(a, attr[a]);
            }
          }
          return iface;
        },

        // set an element invisible for users but not readers
        invisible: function() {
          this.stylize({
            "border":"0 none",
            "clip":"rect(0px, 0px, 0px, 0px)",
            "height":"1px",
            "margin":"-1px",
            "overflow":"hidden",
            "padding":"0",
            "position":"absolute",
            "width":"1px"
          });
          return iface;
        },

        // Sets the HTML
        html: function(content) {
          elem.innerHTML = content;
          return iface;
        },

        // Returns the width of this element
        getWidth: function() {
          return elem.clientWidth;
        },

        // Adds a click handler to this element
        onClick: function(callback) {
          bindEvent(elem, 'click', callback);
          return iface;
        },

        // Removes this element from the DOM
        destroy: function() {
          document.body.removeChild(elem);
          return iface;
        }

      };

      return iface;
    };

    // An interface for generating the grey-out effect
    var overlay = function(getOption) {

      // The registered on click events
      var clickCallbacks = observable();

      // The overlay element
      var elem = make()
        .setAttr({"title":"Close this windows"})
        .clazz("pico-overlay")
        .stylize({
          display: "block",
          position: "fixed",
          top: "0px",
          left: "0px",
          height: "100%",
          width: "100%",
          zIndex: 10000
        })
        .stylize(getOption('overlayStyles', {
          opacity: 0.5,
          background: "#000"
        }))
        .onClick(clickCallbacks.trigger);

      // create a hidden element that permits to be read and that explains we can close the overlay by clicking
      if (getOption('overlayClose', true)) {
        elem.child('span')
          .invisible()
          .setAttr({"tabindex":"0","id":"overlayCloseForReaders"})
          .html(getOption('closeHtml', "Close"));
      }

      return {
        elem: elem.elem,
        destroy: elem.destroy,
        onClick: clickCallbacks.watch
      };
    };

    // Wrap an HTMLElement around another HTMLElement or an array of them.
    // source: http://stackoverflow.com/questions/3337587/wrapping-a-set-of-dom-elements-using-javascript/13169465#13169465
    var wrapAll = function(father, elms) {
        var el = elms.length ? elms[0] : elms;

        // Cache the current parent and sibling of the first element.
        var parent  = el.parentNode;
        var sibling = el.nextSibling;

        // Wrap the first element (is automatically removed from its
        // current parent).
        father.appendChild(el);

        // Wrap all other elements (if applicable). Each element is
        // automatically removed from its current parent and from the elms
        // array.
        while (elms.length) {
            father.appendChild(elms[0]);
        }

        // If the first element had a sibling, insert the wrapper before the
        // sibling to maintain the HTML structure; otherwise, just append it
        // to the parent.
        if (sibling) {
            try { parent.insertBefore(father, sibling) } catch(e) {}
        } else {
            try { parent.appendChild(father) } catch(e) {}
        }
    };

    Array.prototype.indexOf||(Array.prototype.indexOf=function(r,t){var n;if(null==this)throw new TypeError('"this" is null or undefined');var a=Object(this),e=a.length>>>0;if(0===e)return-1;var i=+t||0;if(Math.abs(i)===1/0&&(i=0),i>=e)return-1;for(n=Math.max(i>=0?i:e-Math.abs(i),0);e>n;){if(n in a&&a[n]===r)return n;n++}return-1});

    var findFocusableElements = function(element, ret) {
      //"a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"
      if (!element) return;
      var children = element.children;
      var i = children.length;
      var focusable=false, tabindex, contenteditable;
      if (i > 0) {
        do {
          findFocusableElements(children[i], ret)
        } while(i--);
      } else {
        tabindex = element.getAttribute("tabindex");
        contenteditable = element.getAttribute("contenteditable")
        switch (element.tagName) {
          case "A":
          case "AREA":
          case "INPUT":
          case "SELECT":
          case "TEXTAREA":
          case "BUTTON": {
            if (!element.getAttribute("disabled")) {
              ret.push(element);
              return;
            }
            break;
          }
          case "IFRAME":
          case "OBJECT":
          case "EMBED": {
            ret.push(element);
            return;
          }
        }
        if (contenteditable !== null) {
          ret.push(element);
          return
        }
        if (tabindex !== null && tabindex >= 0) {
          ret.push(element);
          return
        }
      }
    };

    // A function for easily displaying a modal with the given content
    return function(options) {

      if (typeof options === "string") {
        options = {
          content: options
        };
      }

      // we record the focused element
      var focusElement = document.activeElement;

      // the body content must be grouped into a 'div' in order to use aria-hidden on it
      !function() {
        var frag = document.createDocumentFragment();
        var father = document.createElement('div');
        father.setAttribute("aria-hidden", "true");
        father.setAttribute("id", "picoModalProtect");
        wrapAll(father, document.body.children);
        document.body.appendChild(father);
      }();

      // Returns a named option if it has been explicitly defined. Otherwise,
      // it returns the given default value
      function getOption(opt, defaultValue) {
        return options[opt] === void(0) ? defaultValue : options[opt];
      }
      var closeCallbacks = observable();
      var windowSize = getWindowSize();
      var elem = make('dialog')
        .clazz("pico-content")
        .stylize({
          display: 'block',
          boxSizing: 'border-box',
          position: 'fixed',
          zIndex: 10001,
          left: "50%",
          top: "50px",
          overflow: "auto",
          maxWidth: windowSize.width+"px",
          maxHeight: (windowSize.height-100)+"px"
        })
        .setAttr({"role":"dialog"})
        .html(options.content);

      var close = function() {
        shadow.destroy();
        elem.destroy();
        // remove picoModalProtect
        var body = document.body;
        var father = document.getElementById('picoModalProtect');
        wrapAll(body, father.children);
        father.parentNode.removeChild(father);
        // remove bind
        (document.body.removeEventListener ? document.body.removeEventListener('keydown',trapKeyDown,false) : document.body.detachEvent('onkeydown',trapKeyDown));
        // get back the focus
        if (focusElement) focusElement.focus();
        closeCallbacks.trigger();
      };

      // the close button at first
      var closeButton;
      if (getOption('closeButton', true)) {
        closeButton = elem.html("").child('button')
          .html(getOption('closeHtml', "Close"))
          .clazz("pico-close")
          .stylize(getOption('closeStyles', {
            borderRadius: "2px",
            cursor: "pointer",
            height: "auto",
            width: "auto",
            position: "absolute",
            top: "5px",
            right: "5px",
            fontSize: "16px",
            textAlign: "center",
            lineHeight: "15px",
            background: "#CCC"
          }))
          .setAttr({"tabindex":"0"})
          .onClick(close);
          // move the button at the top of the modal
          closeButton.elem.insertAdjacentHTML('afterend', options.content);
      }

      var width = getOption('width', elem.getWidth());

      // for small screens we want a larger popup
      if (windowSize.width < 768) width = windowSize.width - 40;

      elem.stylize({
          minWidth: width + "px",
          margin: "0 0 0 " + (-(width / 2) + "px")
        })
        .stylize(getOption('modalStyles', {
          backgroundColor: "white",
          padding: "20px",
          borderRadius: "5px"
        }));

      var shadow = overlay(getOption);
      if (getOption('overlayClose', true)) {
        shadow.onClick(close);
      }

      // get focus on the close element or on a special message created inside the modal
      var focusInModal;
      if (!options.focusOn) {
        if (getOption('closeButton', true)) {
          focusInModal = closeButton;
          closeButton.elem.focus();
        } else {
          !function() {
            focusInModal = elem.html("").child('span').setAttr({"lang":"en","tabindex":"0"}).html('popup');
            elem.child('div').html(options.content);
            focusInModal.elem.focus();
            setTimeout(function() { focusInModal.invisible() }, 250);
          }();
        }
      } else {
        focusInModal = document.querySelector(getOption('focusOn'));
        if (focusInModal) {
          focusInModal.setAttribute("tabindex","0");
          setTimeout(function() { focusInModal.focus(); }, 0);
        }
      }

      // trap tab and shift+tab inside the modal
      // and ESC to close
      var allowClose = (getOption('closeButton', true) || getOption('overlayClose', true));
      var trapKeyDown = function(event) {
        var code = (event.which ? event.which : event.keyCode);
        if (code == 9) {
          // find focusableelement
          var focusableElements = [], len, index, overlay;
          findFocusableElements(elem.elem, focusableElements);
          focusableElements.reverse();
          len = focusableElements.length;
          index = focusableElements.indexOf(document.activeElement);
          if (index === 0 && event.shiftKey) {
            // if focused on first item and user preses back-tab, go to the last focusable item
            focusableElements[len-1].focus();
            (event.preventDefault ? event.preventDefault() : event.returnValue = false);
          } else {
            if (index === len-1 && !event.shiftKey) {
              // if focused on the last item and user preses tab, go to the first focusable item
              focusableElements[0].focus();
              (event.preventDefault ? event.preventDefault() : event.returnValue = false);
            }
          }
        } else if (code == 27 && allowClose) { // esc
          close();
        }
      };
      bindEvent(document.body, 'keydown', trapKeyDown);

      return {
        modalElem: elem.elem,
        closeElem: closeButton ? closeButton.elem : null,
        overlayElem: shadow.elem,
        close: close,
        onClose: closeCallbacks.watch
      };
    };

  }(window, document));

  return picoModal;
});
<commit_msg>Revert "Use min-width"<commit_after>/*!
 * Original idea: https://github.com/Nycto/PicoModal
 * Accessibility inspiration: https://github.com/nico3333fr/jquery-accessible-modal-window-aria
 * MIT License (https://github.com/Aymkdn/picoModal-Accessible/blob/master/LICENSE)
*/
!function(name, definition) {
  if (typeof module != 'undefined') module.exports = definition()
  else if (typeof define == 'function' && typeof define.amd == 'object') define(definition)
  else this[name] = definition()
}('picoModal', function() {

  var picoModal = (function(window, document) {
    "use strict";

    // Generates observable objects that can be watched and triggered
    var observable = function() {
      var callbacks = [];
      return {
        watch: function(callback) {
          callbacks.push(callback);
        },
        trigger: function() {
          for (var i = 0; i < callbacks.length; i++) {
            window.setTimeout(callbacks[i], 1);
          }
        }
      };
    };

    // bind events
    var bindEvent = function(element, eventName, fct) {
      if (element.addEventListener) element.addEventListener(eventName, fct, false);
      else element.attachEvent("on"+eventName, fct);
    };

    // get window size
    // @return {width,hegiht}
    // source: http://help.dottoro.com/ljmclkbi.php
    var getWindowSize = function() {
      // always return 1, except at non-default zoom levels in IE before version 8
      var GetZoomFactor=function() {
        var factor = 1;
        if (document.body.getBoundingClientRect) {
          // rect is only in physical pixel size in IE before version 8
          var rect = document.body.getBoundingClientRect();
          var physicalW = rect.right - rect.left;
          var logicalW = document.body.offsetWidth;

          // the zoom level is always an integer percent value
          factor = Math.round ((physicalW / logicalW) * 100) / 100;
        }
        return factor;
      };
      var zoomFactor = GetZoomFactor();
      var w = Math.round (document.documentElement.clientWidth / zoomFactor);
      var h = Math.round (document.documentElement.clientHeight / zoomFactor);
      return {width:w, height:h};
    };

    // A small interface for creating and managing a dom element
    var make = function(parent, tag) {
      if (arguments.length == 1 && typeof parent === "string") {
        tag = parent;
        parent = null;
      }
      tag = tag || 'div';

      var elem = document.createElement(tag);
      (parent || document.body).appendChild(elem);

      var iface = {

        elem: elem,

        // Creates a child of this node
        child: function(tag) {
          return make(elem, tag || 'a');
        },

        // Applies a set of styles to an element
        stylize: function(styles) {
          styles = styles || {};

          if (typeof styles.opacity !== "undefined") {
            styles.filter =
              "alpha(opacity=" + (styles.opacity * 100) + ")";
          }

          for (var prop in styles) {
            if (styles.hasOwnProperty(prop)) {
              elem.style[prop] = styles[prop];
            }
          }

          return iface;
        },

        // Adds a class name
        clazz: function(clazz) {
          elem.className += clazz;
          return iface;
        },
        // set attributes
        setAttr: function(attr) {
          for (var a in attr) {
            if (attr.hasOwnProperty(a)) {
              elem.setAttribute(a, attr[a]);
            }
          }
          return iface;
        },

        // set an element invisible for users but not readers
        invisible: function() {
          this.stylize({
            "border":"0 none",
            "clip":"rect(0px, 0px, 0px, 0px)",
            "height":"1px",
            "margin":"-1px",
            "overflow":"hidden",
            "padding":"0",
            "position":"absolute",
            "width":"1px"
          });
          return iface;
        },

        // Sets the HTML
        html: function(content) {
          elem.innerHTML = content;
          return iface;
        },

        // Returns the width of this element
        getWidth: function() {
          return elem.clientWidth;
        },

        // Adds a click handler to this element
        onClick: function(callback) {
          bindEvent(elem, 'click', callback);
          return iface;
        },

        // Removes this element from the DOM
        destroy: function() {
          document.body.removeChild(elem);
          return iface;
        }

      };

      return iface;
    };

    // An interface for generating the grey-out effect
    var overlay = function(getOption) {

      // The registered on click events
      var clickCallbacks = observable();

      // The overlay element
      var elem = make()
        .setAttr({"title":"Close this windows"})
        .clazz("pico-overlay")
        .stylize({
          display: "block",
          position: "fixed",
          top: "0px",
          left: "0px",
          height: "100%",
          width: "100%",
          zIndex: 10000
        })
        .stylize(getOption('overlayStyles', {
          opacity: 0.5,
          background: "#000"
        }))
        .onClick(clickCallbacks.trigger);

      // create a hidden element that permits to be read and that explains we can close the overlay by clicking
      if (getOption('overlayClose', true)) {
        elem.child('span')
          .invisible()
          .setAttr({"tabindex":"0","id":"overlayCloseForReaders"})
          .html(getOption('closeHtml', "Close"));
      }

      return {
        elem: elem.elem,
        destroy: elem.destroy,
        onClick: clickCallbacks.watch
      };
    };

    // Wrap an HTMLElement around another HTMLElement or an array of them.
    // source: http://stackoverflow.com/questions/3337587/wrapping-a-set-of-dom-elements-using-javascript/13169465#13169465
    var wrapAll = function(father, elms) {
        var el = elms.length ? elms[0] : elms;

        // Cache the current parent and sibling of the first element.
        var parent  = el.parentNode;
        var sibling = el.nextSibling;

        // Wrap the first element (is automatically removed from its
        // current parent).
        father.appendChild(el);

        // Wrap all other elements (if applicable). Each element is
        // automatically removed from its current parent and from the elms
        // array.
        while (elms.length) {
            father.appendChild(elms[0]);
        }

        // If the first element had a sibling, insert the wrapper before the
        // sibling to maintain the HTML structure; otherwise, just append it
        // to the parent.
        if (sibling) {
            try { parent.insertBefore(father, sibling) } catch(e) {}
        } else {
            try { parent.appendChild(father) } catch(e) {}
        }
    };

    Array.prototype.indexOf||(Array.prototype.indexOf=function(r,t){var n;if(null==this)throw new TypeError('"this" is null or undefined');var a=Object(this),e=a.length>>>0;if(0===e)return-1;var i=+t||0;if(Math.abs(i)===1/0&&(i=0),i>=e)return-1;for(n=Math.max(i>=0?i:e-Math.abs(i),0);e>n;){if(n in a&&a[n]===r)return n;n++}return-1});

    var findFocusableElements = function(element, ret) {
      //"a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]"
      if (!element) return;
      var children = element.children;
      var i = children.length;
      var focusable=false, tabindex, contenteditable;
      if (i > 0) {
        do {
          findFocusableElements(children[i], ret)
        } while(i--);
      } else {
        tabindex = element.getAttribute("tabindex");
        contenteditable = element.getAttribute("contenteditable")
        switch (element.tagName) {
          case "A":
          case "AREA":
          case "INPUT":
          case "SELECT":
          case "TEXTAREA":
          case "BUTTON": {
            if (!element.getAttribute("disabled")) {
              ret.push(element);
              return;
            }
            break;
          }
          case "IFRAME":
          case "OBJECT":
          case "EMBED": {
            ret.push(element);
            return;
          }
        }
        if (contenteditable !== null) {
          ret.push(element);
          return
        }
        if (tabindex !== null && tabindex >= 0) {
          ret.push(element);
          return
        }
      }
    };

    // A function for easily displaying a modal with the given content
    return function(options) {

      if (typeof options === "string") {
        options = {
          content: options
        };
      }

      // we record the focused element
      var focusElement = document.activeElement;

      // the body content must be grouped into a 'div' in order to use aria-hidden on it
      !function() {
        var frag = document.createDocumentFragment();
        var father = document.createElement('div');
        father.setAttribute("aria-hidden", "true");
        father.setAttribute("id", "picoModalProtect");
        wrapAll(father, document.body.children);
        document.body.appendChild(father);
      }();

      // Returns a named option if it has been explicitly defined. Otherwise,
      // it returns the given default value
      function getOption(opt, defaultValue) {
        return options[opt] === void(0) ? defaultValue : options[opt];
      }
      var closeCallbacks = observable();
      var windowSize = getWindowSize();
      var elem = make('dialog')
        .clazz("pico-content")
        .stylize({
          display: 'block',
          boxSizing: 'border-box',
          position: 'fixed',
          zIndex: 10001,
          left: "50%",
          top: "50px",
          overflow: "auto",
          maxWidth: windowSize.width+"px",
          maxHeight: (windowSize.height-100)+"px"
        })
        .setAttr({"role":"dialog"})
        .html(options.content);

      var close = function() {
        shadow.destroy();
        elem.destroy();
        // remove picoModalProtect
        var body = document.body;
        var father = document.getElementById('picoModalProtect');
        wrapAll(body, father.children);
        father.parentNode.removeChild(father);
        // remove bind
        (document.body.removeEventListener ? document.body.removeEventListener('keydown',trapKeyDown,false) : document.body.detachEvent('onkeydown',trapKeyDown));
        // get back the focus
        if (focusElement) focusElement.focus();
        closeCallbacks.trigger();
      };

      // the close button at first
      var closeButton;
      if (getOption('closeButton', true)) {
        closeButton = elem.html("").child('button')
          .html(getOption('closeHtml', "Close"))
          .clazz("pico-close")
          .stylize(getOption('closeStyles', {
            borderRadius: "2px",
            cursor: "pointer",
            height: "auto",
            width: "auto",
            position: "absolute",
            top: "5px",
            right: "5px",
            fontSize: "16px",
            textAlign: "center",
            lineHeight: "15px",
            background: "#CCC"
          }))
          .setAttr({"tabindex":"0"})
          .onClick(close);
          // move the button at the top of the modal
          closeButton.elem.insertAdjacentHTML('afterend', options.content);
      }

      var width = getOption('width', elem.getWidth());

      // for small screens we want a larger popup
      if (windowSize.width < 768) width = windowSize.width - 40;

      elem.stylize({
          width: width + "px",
          margin: "0 0 0 " + (-(width / 2) + "px")
        })
        .stylize(getOption('modalStyles', {
          backgroundColor: "white",
          padding: "20px",
          borderRadius: "5px"
        }));

      var shadow = overlay(getOption);
      if (getOption('overlayClose', true)) {
        shadow.onClick(close);
      }

      // get focus on the close element or on a special message created inside the modal
      var focusInModal;
      if (!options.focusOn) {
        if (getOption('closeButton', true)) {
          focusInModal = closeButton;
          closeButton.elem.focus();
        } else {
          !function() {
            focusInModal = elem.html("").child('span').setAttr({"lang":"en","tabindex":"0"}).html('popup');
            elem.child('div').html(options.content);
            focusInModal.elem.focus();
            setTimeout(function() { focusInModal.invisible() }, 250);
          }();
        }
      } else {
        focusInModal = document.querySelector(getOption('focusOn'));
        if (focusInModal) {
          focusInModal.setAttribute("tabindex","0");
          setTimeout(function() { focusInModal.focus(); }, 0);
        }
      }

      // trap tab and shift+tab inside the modal
      // and ESC to close
      var allowClose = (getOption('closeButton', true) || getOption('overlayClose', true));
      var trapKeyDown = function(event) {
        var code = (event.which ? event.which : event.keyCode);
        if (code == 9) {
          // find focusableelement
          var focusableElements = [], len, index, overlay;
          findFocusableElements(elem.elem, focusableElements);
          focusableElements.reverse();
          len = focusableElements.length;
          index = focusableElements.indexOf(document.activeElement);
          if (index === 0 && event.shiftKey) {
            // if focused on first item and user preses back-tab, go to the last focusable item
            focusableElements[len-1].focus();
            (event.preventDefault ? event.preventDefault() : event.returnValue = false);
          } else {
            if (index === len-1 && !event.shiftKey) {
              // if focused on the last item and user preses tab, go to the first focusable item
              focusableElements[0].focus();
              (event.preventDefault ? event.preventDefault() : event.returnValue = false);
            }
          }
        } else if (code == 27 && allowClose) { // esc
          close();
        }
      };
      bindEvent(document.body, 'keydown', trapKeyDown);

      return {
        modalElem: elem.elem,
        closeElem: closeButton ? closeButton.elem : null,
        overlayElem: shadow.elem,
        close: close,
        onClose: closeCallbacks.watch
      };
    };

  }(window, document));

  return picoModal;
});<|endoftext|>


<commit_before>// O3S - Deltascape 3.0 Savage
[{
  zoneRegex: /(Deltascape V3.0 \(Savage\)|Unknown Zone \(2B9\))/,
  triggers: [
    {
      id: 'O3S Spellblade Holy',
      regex: /1B:........:(\y{Name}):....:....:006[45]:0000:0000:0000:/,
      alarmText: function(data) {
        if (data.holyTargets[1] != data.me)
          return "";
        return "Stack on YOU";
      },
      alertText: function(data) {
        if (data.holyTargets[1] == data.me)
          return;
        for (var i = 0; i < 4; ++i) {
          if (data.holyTargets[i] == data.me)
            return "Get out";
        }
        return "Stack on " + data.holyTargets[1];
      },
      condition: function(data, matches) {
        data.holyTargets = data.holyTargets || [];
        data.holyTargets.push(matches[1]);
        return data.holyTargets.length == 4;
      },
      run: function(data) {
        delete data.holyTargets;
      }
    },
    {
      id: 'O3S Right Face',
      regex: /(\y{Name}) gains the effect of (?:Unknown_510|Right Face) from/,
      infoText: 'Mindjack: Right',
      condition: function(data, matches) { return matches[1] == data.me; },
    },
    {
      id: 'O3S Forward March',
      regex: /(\y{Name}) gains the effect of (?:Unknown_50D|Forward March) from/,
      infoText: 'Mindjack: Forward',
      condition: function(data, matches) { return matches[1] == data.me; },
    },
    {
      id: 'O3S Left Face',
      regex: /(\y{Name}) gains the effect of (?:Unknown_50F|Left Face) from/,
      infoText: 'Mindjack: Left',
      condition: function(data, matches) { return matches[1] == data.me; },
    },
    {
      id: 'O3S About Face',
      regex: /(\y{Name}) gains the effect of (?:Unknown_50E|About Face) from/,
      infoText: 'Mindjack: Back',
      condition: function(data, matches) { return matches[1] == data.me; },
    },
    {
      id: 'O3S Ribbit',
      regex: /:22F7:Halicarnassus starts using/,
      alertText: 'Ribbit: Get behind',
    },
    {
      id: 'O3S Oink',
      regex: /:22F9:Halicarnassus starts using/,
      infoText: 'Oink: Stack',
    },
    {
      id: 'O3S Squelch',
      regex: /:22F8:Halicarnassus starts using/,
      alarmText: 'Squelch: Look away',
    },
    {
      id: 'O3S The Queen\'s Waltz: Books',
      regex: /:230E:Halicarnassus starts using/,
      alertText: 'The Queen\'s Waltz: Books',
    },
    {
      id: 'O3S The Queen\'s Waltz: Clock',
      regex: /:2306:Halicarnassus starts using/,
      infoText: 'The Queen\'s Waltz: Clock',
    },
    {
      id: 'O3S The Queen\'s Waltz: Crystal Square',
      regex: /:230A:Halicarnassus starts using/,
      infoText: 'The Queen\'s Waltz: Crystal Square',
    },
    {
      id: 'O3S The Queen\'s Waltz: Tethers',
      regex: /:2308:Halicarnassus starts using/,
      infoText: 'The Queen\'s Waltz: Tethers',
    },
  ]
}]<commit_msg>Fix o3s library phase triggers<commit_after>// O3S - Deltascape 3.0 Savage
[{
  zoneRegex: /(Deltascape V3.0 \(Savage\)|Unknown Zone \(2B9\))/,
  triggers: [
    {
      id: 'O3S Phase Counter',
      regex: /:Halicarnassus starts Panel Swap/,
      run: function(data) {
        data.phase = (data.phase || 0) + 1;

        // In case something went awry, clean up any holy targets
        // so the next spellblade holy can start afresh.
        delete data.holyTargets;
      },
    },
    {
      // Normal spellblade holy with tethers and one stack point.
      // "64" is a stack marker.  "65" is the prey marker.
      // The debuff order in the logs is:
      //   (1) stack marker (tethered to #2)
      //   (2) prey marker (tethered to #1)
      //   (3) prey marker (tethered to #4)
      //   (4) prey marker (tethered to #3)
      // So, #2 is the person everybody should stack on.
      id: 'O3S Spellblade Holy',
      regex: /1B:........:(\y{Name}):....:....:006[45]:0000:0000:0000:/,
      alarmText: function(data) {
        if (data.holyTargets[1] != data.me)
          return "";
        return "Stack on YOU";
      },
      alertText: function(data) {
        if (data.holyTargets[1] == data.me)
          return;
        for (var i = 0; i < 4; ++i) {
          if (data.holyTargets[i] == data.me)
            return "Get out";
        }
        return "Stack on " + data.holyTargets[1];
      },
      condition: function(data, matches) {
        // Library phase stack markers behave differently.
        if (data.phase == 3)
          return;
        data.holyTargets = data.holyTargets || [];
        data.holyTargets.push(matches[1]);
        return data.holyTargets.length == 4;
      },
      run: function(data) {
        delete data.holyTargets;
      }
    },
    {
      // Library phase spellblade holy with 2 stacks / 4 preys / 2 unmarked.
      id: 'O3S Library Spellblade',
      regex: /1B:........:(\y{Name}):....:....:(006[45]):0000:0000:0000:/,
      alertText: function(data) {
        if (data.librarySpellbladePrinted)
          return;
        data.librarySpellbladePrinted = true;
        if (data.librarySpellbladeMe == "0064")
          return "outside south (stack)";
        if (data.librarySpellbladeMe == "0065")
          return "go north";
        return "inside south (unmarked stack)";
      },
      // Because people can be dead and there are eight marks, delay to
      // accumulate logs instead of counting marks.  Instantly print if
      // anything is on you.  The 6 triggers will all have condition=true
      // and run, but only the first one will print.
      delaySeconds: function(data, matches) {
        return matches[1] == data.me ? 0 : 0.5;
      },
      condition: function(data, matches) {
        // This is only for library phase.
        if (data.phase != 3)
          return;
        if (matches[1] == data.me)
          data.librarySpellbladeMe = matches[2];
        return true;
      },
    },
    {
      id: 'O3S Right Face',
      regex: /(\y{Name}) gains the effect of (?:Unknown_510|Right Face) from/,
      infoText: 'Mindjack: Right',
      condition: function(data, matches) { return matches[1] == data.me; },
    },
    {
      id: 'O3S Forward March',
      regex: /(\y{Name}) gains the effect of (?:Unknown_50D|Forward March) from/,
      infoText: 'Mindjack: Forward',
      condition: function(data, matches) { return matches[1] == data.me; },
    },
    {
      id: 'O3S Left Face',
      regex: /(\y{Name}) gains the effect of (?:Unknown_50F|Left Face) from/,
      infoText: 'Mindjack: Left',
      condition: function(data, matches) { return matches[1] == data.me; },
    },
    {
      id: 'O3S About Face',
      regex: /(\y{Name}) gains the effect of (?:Unknown_50E|About Face) from/,
      infoText: 'Mindjack: Back',
      condition: function(data, matches) { return matches[1] == data.me; },
    },
    {
      id: 'O3S Ribbit',
      regex: /:22F7:Halicarnassus starts using/,
      alertText: 'Ribbit: Get behind',
    },
    {
      id: 'O3S Oink',
      regex: /:22F9:Halicarnassus starts using/,
      infoText: 'Oink: Stack',
    },
    {
      id: 'O3S Squelch',
      regex: /:22F8:Halicarnassus starts using/,
      alarmText: 'Squelch: Look away',
    },
    {
      id: 'O3S The Queen\'s Waltz: Books',
      regex: /:230E:Halicarnassus starts using/,
      alertText: 'The Queen\'s Waltz: Books',
    },
    {
      id: 'O3S The Queen\'s Waltz: Clock',
      regex: /:2306:Halicarnassus starts using/,
      infoText: 'The Queen\'s Waltz: Clock',
    },
    {
      id: 'O3S The Queen\'s Waltz: Crystal Square',
      regex: /:230A:Halicarnassus starts using/,
      infoText: 'The Queen\'s Waltz: Crystal Square',
    },
    {
      id: 'O3S The Queen\'s Waltz: Tethers',
      regex: /:2308:Halicarnassus starts using/,
      infoText: 'The Queen\'s Waltz: Tethers',
    },
  ]
}]
<|endoftext|>