forked from sent/waves
470 lines
20 KiB
JavaScript
470 lines
20 KiB
JavaScript
var InputWritr;
|
|
(function (InputWritr_1) {
|
|
"use strict";
|
|
/**
|
|
* A general utility for automating interactions with user-called events linked
|
|
* with callbacks. Pipe functions are available that take in user input, switch
|
|
* on the event code, and call the appropriate callback. These Pipe functions
|
|
* can be made during runtime; further utilities allow for saving and playback
|
|
* of input histories in JSON format.
|
|
*/
|
|
var InputWritr = (function () {
|
|
/**
|
|
* @param {IInputWritrSettings} settings
|
|
*/
|
|
function InputWritr(settings) {
|
|
if (typeof settings === "undefined") {
|
|
throw new Error("No settings object given to InputWritr.");
|
|
}
|
|
if (typeof settings.triggers === "undefined") {
|
|
throw new Error("No triggers given to InputWritr.");
|
|
}
|
|
this.triggers = settings.triggers;
|
|
// Headless browsers like PhantomJS might not contain the performance
|
|
// class, so Date.now is used as a backup
|
|
if (typeof settings.getTimestamp === "undefined") {
|
|
if (typeof performance === "undefined") {
|
|
this.getTimestamp = function () {
|
|
return Date.now();
|
|
};
|
|
}
|
|
else {
|
|
this.getTimestamp = (performance.now
|
|
|| performance.webkitNow
|
|
|| performance.mozNow
|
|
|| performance.msNow
|
|
|| performance.oNow).bind(performance);
|
|
}
|
|
}
|
|
else {
|
|
this.getTimestamp = settings.getTimestamp;
|
|
}
|
|
this.eventInformation = settings.eventInformation;
|
|
this.canTrigger = settings.hasOwnProperty("canTrigger")
|
|
? settings.canTrigger
|
|
: function () {
|
|
return true;
|
|
};
|
|
this.isRecording = settings.hasOwnProperty("isRecording")
|
|
? settings.isRecording
|
|
: function () {
|
|
return true;
|
|
};
|
|
this.history = {};
|
|
this.histories = {
|
|
"length": 0
|
|
};
|
|
this.aliases = {};
|
|
this.addAliases(settings.aliases || {});
|
|
this.keyAliasesToCodes = settings.keyAliasesToCodes || {
|
|
"shift": 16,
|
|
"ctrl": 17,
|
|
"space": 32,
|
|
"left": 37,
|
|
"up": 38,
|
|
"right": 39,
|
|
"down": 40
|
|
};
|
|
this.keyCodesToAliases = settings.keyCodesToAliases || {
|
|
"16": "shift",
|
|
"17": "ctrl",
|
|
"32": "space",
|
|
"37": "left",
|
|
"38": "up",
|
|
"39": "right",
|
|
"40": "down"
|
|
};
|
|
}
|
|
/**
|
|
* Clears the currently tracked inputs history and resets the starting time,
|
|
* and (optionally) saves the current history.
|
|
*
|
|
* @param {Boolean} [keepHistory] Whether the currently tracked history
|
|
* of inputs should be added to the master
|
|
* Array of histories (defaults to true).
|
|
*/
|
|
InputWritr.prototype.restartHistory = function (keepHistory) {
|
|
if (keepHistory === void 0) { keepHistory = true; }
|
|
if (keepHistory) {
|
|
this.saveHistory(this.history);
|
|
}
|
|
this.history = {};
|
|
this.startingTime = this.getTimestamp();
|
|
};
|
|
/* Simple gets
|
|
*/
|
|
/**
|
|
* @return {Object} The stored mapping of aliases to values.
|
|
*/
|
|
InputWritr.prototype.getAliases = function () {
|
|
return this.aliases;
|
|
};
|
|
/**
|
|
* @return {Object} The stored mapping of aliases to values, with values
|
|
* mapped to their equivalent key Strings.
|
|
*/
|
|
InputWritr.prototype.getAliasesAsKeyStrings = function () {
|
|
var output = {}, alias;
|
|
for (alias in this.aliases) {
|
|
if (this.aliases.hasOwnProperty(alias)) {
|
|
output[alias] = this.getAliasAsKeyStrings(alias);
|
|
}
|
|
}
|
|
return output;
|
|
};
|
|
/**
|
|
* @param {Mixed} alias An alias allowed to be passed in, typically a
|
|
* character code.
|
|
* @return {String[]} The mapped key Strings corresponding to that alias,
|
|
* typically the human-readable Strings representing
|
|
* input names, such as "a" or "left".
|
|
*/
|
|
InputWritr.prototype.getAliasAsKeyStrings = function (alias) {
|
|
return this.aliases[alias].map(this.convertAliasToKeyString.bind(this));
|
|
};
|
|
/**
|
|
* @param {Mixed} alias The alias of an input, typically a character
|
|
* code.
|
|
* @return {String} The human-readable String representing the input name,
|
|
* such as "a" or "left".
|
|
*/
|
|
InputWritr.prototype.convertAliasToKeyString = function (alias) {
|
|
if (alias.constructor === String) {
|
|
return alias;
|
|
}
|
|
if (alias > 96 && alias < 105) {
|
|
return String.fromCharCode(alias - 48);
|
|
}
|
|
if (alias > 64 && alias < 97) {
|
|
return String.fromCharCode(alias);
|
|
}
|
|
return typeof this.keyCodesToAliases[alias] !== "undefined"
|
|
? this.keyCodesToAliases[alias] : "?";
|
|
};
|
|
/**
|
|
* @param {Mixed} key The number code of an input.
|
|
* @return {Number} The machine-usable character code of the input.
|
|
*
|
|
*/
|
|
InputWritr.prototype.convertKeyStringToAlias = function (key) {
|
|
if (key.constructor === Number) {
|
|
return key;
|
|
}
|
|
if (key.length === 1) {
|
|
return key.charCodeAt(0) - 32;
|
|
}
|
|
return typeof this.keyAliasesToCodes[key] !== "undefined" ? this.keyAliasesToCodes[key] : -1;
|
|
};
|
|
/**
|
|
* Get function for a single history, either the current or a past one.
|
|
*
|
|
* @param {String} [name] The identifier for the old history to return. If
|
|
* none is provided, the current history is used.
|
|
* @return {Object} A history of inputs in JSON-friendly form.
|
|
*/
|
|
InputWritr.prototype.getHistory = function (name) {
|
|
if (name === void 0) { name = undefined; }
|
|
return arguments.length ? this.histories[name] : history;
|
|
};
|
|
/**
|
|
* @return {Object} All previously stored histories.
|
|
*/
|
|
InputWritr.prototype.getHistories = function () {
|
|
return this.histories;
|
|
};
|
|
/**
|
|
* @return {Boolean} Whether this is currently allowing inputs.
|
|
*/
|
|
InputWritr.prototype.getCanTrigger = function () {
|
|
return this.canTrigger;
|
|
};
|
|
/**
|
|
* @return {Boolean} Whether this is currently recording allowed inputs.
|
|
*/
|
|
InputWritr.prototype.getIsRecording = function () {
|
|
return this.isRecording;
|
|
};
|
|
/* Simple sets
|
|
*/
|
|
/**
|
|
* @param {Mixed} canTriggerNew Whether this is now allowing inputs. This
|
|
* may be either a Function (to be evaluated
|
|
* on each input) or a general Boolean.
|
|
*/
|
|
InputWritr.prototype.setCanTrigger = function (canTriggerNew) {
|
|
if (canTriggerNew.constructor === Boolean) {
|
|
this.canTrigger = function () {
|
|
return canTriggerNew;
|
|
};
|
|
}
|
|
else {
|
|
this.canTrigger = canTriggerNew;
|
|
}
|
|
};
|
|
/**
|
|
* @param {Boolean} isRecordingNew Whether this is now recording allowed
|
|
* inputs.
|
|
*/
|
|
InputWritr.prototype.setIsRecording = function (isRecordingNew) {
|
|
if (isRecordingNew.constructor === Boolean) {
|
|
this.isRecording = function () {
|
|
return isRecordingNew;
|
|
};
|
|
}
|
|
else {
|
|
this.isRecording = isRecordingNew;
|
|
}
|
|
};
|
|
/**
|
|
* @param {Mixed} eventInformationNew A new first argument for event
|
|
* callbacks.
|
|
*/
|
|
InputWritr.prototype.setEventInformation = function (eventInformationNew) {
|
|
this.eventInformation = eventInformationNew;
|
|
};
|
|
/* Aliases
|
|
*/
|
|
/**
|
|
* Adds a list of values by which an event may be triggered.
|
|
*
|
|
* @param {String} name The name of the event that is being given
|
|
* aliases, such as "left".
|
|
* @param {Array} values An array of aliases by which the event will also
|
|
* be callable.
|
|
*/
|
|
InputWritr.prototype.addAliasValues = function (name, values) {
|
|
var triggerName, triggerGroup, i;
|
|
if (!this.aliases.hasOwnProperty(name)) {
|
|
this.aliases[name] = values;
|
|
}
|
|
else {
|
|
this.aliases[name].push.apply(this.aliases[name], values);
|
|
}
|
|
// triggerName = "onkeydown", "onkeyup", ...
|
|
for (triggerName in this.triggers) {
|
|
if (this.triggers.hasOwnProperty(triggerName)) {
|
|
// triggerGroup = { "left": function, ... }, ...
|
|
triggerGroup = this.triggers[triggerName];
|
|
if (triggerGroup.hasOwnProperty(name)) {
|
|
// values[i] = 37, 65, ...
|
|
for (i = 0; i < values.length; i += 1) {
|
|
triggerGroup[values[i]] = triggerGroup[name];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Removes a list of values by which an event may be triggered.
|
|
*
|
|
* @param {String} name The name of the event that is having aliases
|
|
* removed, such as "left".
|
|
* @param {Array} values An array of aliases by which the event will no
|
|
* longer be callable.
|
|
*/
|
|
InputWritr.prototype.removeAliasValues = function (name, values) {
|
|
var triggerName, triggerGroup, i;
|
|
if (!this.aliases.hasOwnProperty(name)) {
|
|
return;
|
|
}
|
|
for (i = 0; i < values.length; i += 1) {
|
|
this.aliases[name].splice(this.aliases[name].indexOf(values[i], 1));
|
|
}
|
|
// triggerName = "onkeydown", "onkeyup", ...
|
|
for (triggerName in this.triggers) {
|
|
if (this.triggers.hasOwnProperty(triggerName)) {
|
|
// triggerGroup = { "left": function, ... }, ...
|
|
triggerGroup = this.triggers[triggerName];
|
|
if (triggerGroup.hasOwnProperty(name)) {
|
|
// values[i] = 37, 65, ...
|
|
for (i = 0; i < values.length; i += 1) {
|
|
if (triggerGroup.hasOwnProperty(values[i])) {
|
|
delete triggerGroup[values[i]];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Shortcut to remove old alias values and add new ones in.
|
|
*
|
|
*
|
|
* @param {String} name The name of the event that is having aliases
|
|
* added and removed, such as "left".
|
|
* @param {Array} valuesOld An array of aliases by which the event will no
|
|
* longer be callable.
|
|
* @param {Array} valuesNew An array of aliases by which the event will
|
|
* now be callable.
|
|
*/
|
|
InputWritr.prototype.switchAliasValues = function (name, valuesOld, valuesNew) {
|
|
this.removeAliasValues(name, valuesOld);
|
|
this.addAliasValues(name, valuesNew);
|
|
};
|
|
/**
|
|
* Adds a set of alises from an Object containing "name" => [values] pairs.
|
|
*
|
|
* @param {Object} aliasesRaw
|
|
*/
|
|
InputWritr.prototype.addAliases = function (aliasesRaw) {
|
|
var aliasName;
|
|
for (aliasName in aliasesRaw) {
|
|
if (aliasesRaw.hasOwnProperty(aliasName)) {
|
|
this.addAliasValues(aliasName, aliasesRaw[aliasName]);
|
|
}
|
|
}
|
|
};
|
|
/* Functions
|
|
*/
|
|
/**
|
|
* Adds a triggerable event by marking a new callback under the trigger's
|
|
* triggers. Any aliases for the label are also given the callback.
|
|
*
|
|
* @param {String} trigger The name of the triggered event.
|
|
* @param {Mixed} label The code within the trigger to call within,
|
|
* typically either a character code or an alias.
|
|
* @param {Function} callback The callback Function to be triggered.
|
|
*/
|
|
InputWritr.prototype.addEvent = function (trigger, label, callback) {
|
|
var i;
|
|
if (!this.triggers.hasOwnProperty(trigger)) {
|
|
throw new Error("Unknown trigger requested: '" + trigger + "'.");
|
|
}
|
|
this.triggers[trigger][label] = callback;
|
|
if (this.aliases.hasOwnProperty(label)) {
|
|
for (i = 0; i < this.aliases[label].length; i += 1) {
|
|
this.triggers[trigger][this.aliases[label][i]] = callback;
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Removes a triggerable event by deleting any callbacks under the trigger's
|
|
* triggers. Any aliases for the label are also given the callback.
|
|
*
|
|
* @param {String} trigger The name of the triggered event.
|
|
* @param {Mixed} label The code within the trigger to call within,
|
|
* typically either a character code or an alias.
|
|
*/
|
|
InputWritr.prototype.removeEvent = function (trigger, label) {
|
|
var i;
|
|
if (!this.triggers.hasOwnProperty(trigger)) {
|
|
throw new Error("Unknown trigger requested: '" + trigger + "'.");
|
|
}
|
|
delete this.triggers[trigger][label];
|
|
if (this.aliases.hasOwnProperty(label)) {
|
|
for (i = 0; i < this.aliases[label].length; i += 1) {
|
|
if (this.triggers[trigger][this.aliases[label][i]]) {
|
|
delete this.triggers[trigger][this.aliases[label][i]];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Stores the current history in the histories listing. this.restartHistory
|
|
* is typically called directly after.
|
|
*/
|
|
InputWritr.prototype.saveHistory = function (name) {
|
|
if (name === void 0) { name = undefined; }
|
|
this.histories[this.histories.length] = history;
|
|
this.histories.length += 1;
|
|
if (arguments.length) {
|
|
this.histories[name] = history;
|
|
}
|
|
};
|
|
/**
|
|
* Plays back the current history using this.playEvents.
|
|
*/
|
|
InputWritr.prototype.playHistory = function () {
|
|
this.playEvents(this.history);
|
|
};
|
|
/**
|
|
* "Plays" back an Array of event information by simulating each keystroke
|
|
* in a new call, timed by setTimeout.
|
|
*
|
|
* @param {Object} events The events history to play back.
|
|
* @remarks This will execute the same actions in the same order as before,
|
|
* but the arguments object may be different.
|
|
*/
|
|
InputWritr.prototype.playEvents = function (events) {
|
|
var timeouts = {}, time, call;
|
|
for (time in events) {
|
|
if (events.hasOwnProperty(time)) {
|
|
call = this.makeEventCall(events[time]);
|
|
timeouts[time] = setTimeout(call, (Number(time) - this.startingTime) | 0);
|
|
}
|
|
}
|
|
};
|
|
/**
|
|
* Primary driver function to run an event. The event is chosen from the
|
|
* triggers object, and called with eventInformation as the input.
|
|
*
|
|
* @param {Function, String} event The event function (or string alias of
|
|
* it) that will be called.
|
|
* @param {Mixed} [keyCode] The alias of the event function under
|
|
* triggers[event], if event is a String.
|
|
* @param {Event} [sourceEvent] The raw event that caused the calling Pipe
|
|
* to be triggered, such as a MouseEvent.
|
|
* @return {Mixed}
|
|
*/
|
|
InputWritr.prototype.callEvent = function (event, keyCode, sourceEvent) {
|
|
if (!this.canTrigger(event, keyCode)) {
|
|
return;
|
|
}
|
|
if (!event) {
|
|
throw new Error("Blank event given, ignoring it.");
|
|
}
|
|
if (event.constructor === String) {
|
|
event = this.triggers[event][keyCode];
|
|
}
|
|
return event(this.eventInformation, sourceEvent);
|
|
};
|
|
/**
|
|
* Creates and returns a function to run a trigger.
|
|
*
|
|
* @param {String} trigger The label for the Array of functions that the
|
|
* pipe function should choose from.
|
|
* @param {String} codeLabel A mapping String for the alias to get the
|
|
* alias from the event.
|
|
* @param {Boolean} [preventDefaults] Whether the input to the pipe
|
|
* function will be an HTML-style
|
|
* event, where .preventDefault()
|
|
* should be clicked.
|
|
* @return {Function}
|
|
*/
|
|
InputWritr.prototype.makePipe = function (trigger, codeLabel, preventDefaults) {
|
|
var functions = this.triggers[trigger], InputWriter = this;
|
|
if (!functions) {
|
|
throw new Error("No trigger of label '" + trigger + "' defined.");
|
|
}
|
|
return function Pipe(event) {
|
|
var alias = event[codeLabel];
|
|
// Typical usage means alias will be an event from a key/mouse input
|
|
if (preventDefaults && event.preventDefault instanceof Function) {
|
|
event.preventDefault();
|
|
}
|
|
// If there's a function under that alias, run it
|
|
if (functions.hasOwnProperty(alias)) {
|
|
if (InputWriter.isRecording()) {
|
|
InputWriter.history[InputWriter.getTimestamp() | 0] = [trigger, alias];
|
|
}
|
|
InputWriter.callEvent(functions[alias], alias, event);
|
|
}
|
|
};
|
|
};
|
|
/**
|
|
* Curry utility to create a closure that runs call() when called.
|
|
*
|
|
* @param {Array} info An array containing [alias, keyCode].
|
|
* @return {Function} A closure Function that activates a trigger
|
|
* when called.
|
|
*/
|
|
InputWritr.prototype.makeEventCall = function (info) {
|
|
return function () {
|
|
this.callEvent(info[0], info[1]);
|
|
};
|
|
};
|
|
return InputWritr;
|
|
})();
|
|
InputWritr_1.InputWritr = InputWritr;
|
|
})(InputWritr || (InputWritr = {}));
|