Initial commit (a clone of the jsmachines project as of v1.15.3)
This commit is contained in:
commit
a5e3e6a59d
714 changed files with 130602 additions and 0 deletions
28
my_modules/shared/lib/README.md
Normal file
28
my_modules/shared/lib/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
Shared Sources
|
||||
===
|
||||
|
||||
This folder contains a mix of shared code, with some files used only by Node (server) modules,
|
||||
some used only by Browser (client) modules, and others used by both.
|
||||
|
||||
At the moment, only a few files are completely agnostic; eg: [strlib.js](strlib.js) and [usrlib.js](usrlib.js).
|
||||
One give-away is that neither contain references to any globals (although references to each other
|
||||
would be fine).
|
||||
|
||||
[netlib.js](netlib.js) is appropriate only for Node modules, because it contains code that relies on Node's
|
||||
global *Buffer* object, as indicated by:
|
||||
|
||||
/* global Buffer: false */
|
||||
|
||||
And [weblib.js](weblib.js) is appropriate only for client modules, because it contains code that relies on the
|
||||
brower's global *window* object, as indicated by:
|
||||
|
||||
/* global window: true */
|
||||
|
||||
We declare *window* modifiable (true) so that [defines.js](defines.js) can set *global.window* to *false*
|
||||
when running within Node, allowing any other code to test the existence of *window* with a simple:
|
||||
|
||||
if (window) {...}
|
||||
|
||||
instead of:
|
||||
|
||||
if (typeof window !== 'undefined') {...}
|
||||
906
my_modules/shared/lib/component.js
Normal file
906
my_modules/shared/lib/component.js
Normal file
|
|
@ -0,0 +1,906 @@
|
|||
/**
|
||||
* @fileoverview The Component class used by C1Pjs and PCjs.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-May-14
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* All the C1Pjs and PCjs components now use JSDoc types, primarily so that Google's Closure Compiler
|
||||
* will compile everything with ZERO warnings. For more information about the JSDoc types supported by
|
||||
* the Closure Compiler:
|
||||
*
|
||||
* https://developers.google.com/closure/compiler/docs/js-for-compiler#types
|
||||
*
|
||||
* I also attempted to use JSLint, but it's excessively strict for my taste, so this is the only file
|
||||
* I tried massaging for JSLint's sake. I gave up when it complained about my use of "while (true)";
|
||||
* replacing "true" with an assignment expression didn't make it any happier.
|
||||
*
|
||||
* I wasn't thrilled about replacing all "++" and "--" operators with "+= 1" and "-= 1", nor about using
|
||||
* "(s || '')" instead of "(s? s : '')", because while the former may seem simpler, it is NOT more portable.
|
||||
* It's not that I'm trying to write "portable JavaScript", but some of this code was ported from C code
|
||||
* I'd written about 14 years earlier, and portability is good, so I see no reason to rewrite code to make
|
||||
* it less portable.
|
||||
*
|
||||
* UPDATE: I've since switched to JSHint, which seems to have more reasonable defaults.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global window: true, DEBUG: true */
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
require("./defines");
|
||||
var usr = require("./usrlib");
|
||||
var web = require("./weblib");
|
||||
}
|
||||
|
||||
/**
|
||||
* Component(type, parms, constructor)
|
||||
*
|
||||
* @constructor
|
||||
* @param {string} type
|
||||
* @param {Object} [parms]
|
||||
* @param {Object} [constructor]
|
||||
*
|
||||
* A Component object requires:
|
||||
*
|
||||
* type: a user-defined type name (eg, "CPU")
|
||||
*
|
||||
* and accepts any or all of the following (parms) properties:
|
||||
*
|
||||
* id: component ID (default is "")
|
||||
* name: component name (default is ""; if blank, toString() will use the type name only)
|
||||
* comment: component comment string (default is undefined)
|
||||
*
|
||||
* Subclasses that use Component.subclass() to extend Component will likely have additional (parms) properties.
|
||||
*/
|
||||
function Component(type, parms, constructor)
|
||||
{
|
||||
this.type = type;
|
||||
|
||||
if (!parms) {
|
||||
parms = {'id': "", 'name': ""};
|
||||
}
|
||||
|
||||
this.id = parms['id'];
|
||||
this.name = parms['name'];
|
||||
this.comment = parms['comment'];
|
||||
this.parms = parms;
|
||||
if (this.id === undefined) this.id = "";
|
||||
|
||||
var i = this.id.indexOf('.');
|
||||
if (i > 0) {
|
||||
this.idMachine = this.id.substr(0, i);
|
||||
this.idComponent = this.id.substr(i + 1);
|
||||
} else {
|
||||
this.idComponent = this.id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Recording the constructor is really just a debugging aid, because many of our constructors
|
||||
* have class constants, but they're hard to find when the constructors are buried among all the
|
||||
* other globals.
|
||||
*/
|
||||
this[type] = constructor;
|
||||
|
||||
/*
|
||||
* TODO: Decide how to reintegrate this code into the components that still want it....
|
||||
*
|
||||
if (this.initStep) this.initStep(parms);
|
||||
*/
|
||||
this.fnReady = null;
|
||||
this.fReady = false;
|
||||
this.fBusy = this.fBusyCancel = false;
|
||||
this.fPowered = false;
|
||||
this.clearError();
|
||||
this.bindings = {};
|
||||
this.dbg = null; // by default, no connection to a Debugger
|
||||
|
||||
this.getMachineNum(); // once again, make the function appear used
|
||||
|
||||
Component.add(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component.parmsURL
|
||||
*
|
||||
* Initialized to the set of URL parameters, if any, for the current web page.
|
||||
*
|
||||
* @type {Object|null}
|
||||
*/
|
||||
Component.parmsURL = web.getURLParameters();
|
||||
|
||||
/**
|
||||
* Component.inherit(p)
|
||||
*
|
||||
* Returns a newly created object that inherits properties from the prototype object p.
|
||||
* It uses the ECMAScript 5 function Object.create() if it is defined, and otherwise falls back to an older technique.
|
||||
*
|
||||
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 6-1)
|
||||
*
|
||||
* @param {Object} p
|
||||
*/
|
||||
Component.inherit = function(p)
|
||||
{
|
||||
if (window) { // an alternative to "if (typeof window === 'undefined')" if require("defines") has been invoked
|
||||
if (!p) throw new TypeError(); // TODO: Why does this barf under Node?
|
||||
if (Object.create) {
|
||||
return Object.create(p);
|
||||
}
|
||||
var t = typeof p;
|
||||
if (t !== "object" && t !== "function") throw new TypeError();
|
||||
}
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function F() {}
|
||||
F.prototype = p;
|
||||
return new F();
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.extend(o, p)
|
||||
*
|
||||
* Copies the enumerable properties of p to o and returns o.
|
||||
* If o and p have a property by the same name, o's property is overwritten.
|
||||
*
|
||||
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 6-2)
|
||||
*
|
||||
* @param {Object} o
|
||||
* @param {Object} p
|
||||
*/
|
||||
Component.extend = function(o, p)
|
||||
{
|
||||
for (var prop in p) {
|
||||
o[prop] = p[prop];
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.subclass(superclass, subclass, methods, statics)
|
||||
*
|
||||
* TODO: Determine why every subclass created by this function ends up with a name prefix of "Component.subclass"
|
||||
* in Chrome's call stack, rather than the (more logical) name of the subclass constructor. Is there a different
|
||||
* design pattern I should be using that creates subclasses more to Chrome's liking?
|
||||
*
|
||||
* See: Flanagan, David (2011-04-18). JavaScript: The Definitive Guide: The Definitive Guide (Kindle Locations 9854-9903). OReilly Media - A. Kindle Edition (Example 9-11)
|
||||
*
|
||||
* @param {Object} superclass is the constructor of the superclass
|
||||
* @param {Object} subclass is the constructor for the new subclass
|
||||
* @param {Object} [methods] contains all instance methods
|
||||
* @param {Object} [statics] contains all class properties and methods
|
||||
*/
|
||||
Component.subclass = function(superclass, subclass, methods, statics)
|
||||
{
|
||||
subclass.prototype = Component.inherit(superclass.prototype);
|
||||
subclass.prototype.constructor = subclass;
|
||||
if (methods) {
|
||||
Component.extend(subclass.prototype, methods);
|
||||
}
|
||||
if (statics) {
|
||||
Component.extend(subclass, statics);
|
||||
}
|
||||
return subclass;
|
||||
};
|
||||
|
||||
/*
|
||||
* Every component created on the current page is recorded in this array (see Component.add()).
|
||||
*
|
||||
* This enables any component to locate another component by ID (see Component.getComponentByID())
|
||||
* or by type (see Component.getComponentByType()).
|
||||
*/
|
||||
Component.all = [];
|
||||
|
||||
/**
|
||||
* Component.add(component)
|
||||
*
|
||||
* @param {Component} component
|
||||
*/
|
||||
Component.add = function(component)
|
||||
{
|
||||
/*
|
||||
* This just generates a lot of useless noise, handy in the early days, not so much these days...
|
||||
*
|
||||
* Component.log("Component.add(" + component.type + "," + component.id + ")");
|
||||
*/
|
||||
Component.all[Component.all.length] = component;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.log(s, type)
|
||||
*
|
||||
* For diagnostic output only.
|
||||
*
|
||||
* @param {string} [s] is the message text
|
||||
* @param {string} [type] is the message type
|
||||
*/
|
||||
Component.log = function(s, type)
|
||||
{
|
||||
if (DEBUG) {
|
||||
var msElapsed, sMsg = (type? (type + ": ") : "") + (s || "");
|
||||
if (Component.msStart === undefined) {
|
||||
Component.msStart = usr.getTime();
|
||||
}
|
||||
msElapsed = usr.getTime() - Component.msStart;
|
||||
console.log(sMsg? (msElapsed + "ms: " + sMsg.replace(/\n/g, " ")) : "");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.assert(f, s)
|
||||
*
|
||||
* Used to verify conditions that must be true (for DEBUG builds only; compiled builds should automatically have all
|
||||
* references to Component.assert() removed).
|
||||
*
|
||||
* @param {boolean} f is the expression we are asserting to be true
|
||||
* @param {string} [s] is description of the assertion on failure
|
||||
*/
|
||||
Component.assert = function(f, s)
|
||||
{
|
||||
if (DEBUG) {
|
||||
if (!f) {
|
||||
if (!s) {
|
||||
/*
|
||||
* TODO: An accompanying source file/line number/function call would be nice, if there was a browser-independent way....
|
||||
*/
|
||||
s = "assertion failure";
|
||||
}
|
||||
Component.log(s);
|
||||
throw new Error(s);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.println(s, type, id)
|
||||
*
|
||||
* For non-diagnostic output, which some components override in order to make their output visible in their own way.
|
||||
*
|
||||
* @param {string} [s] is the message text
|
||||
* @param {string} [type] is the message type
|
||||
* @param {string} [id] is the caller's ID, if any
|
||||
*/
|
||||
Component.println = function(s, type, id)
|
||||
{
|
||||
if (DEBUG) {
|
||||
Component.log((id? (id + ": ") : "") + (s? ("\"" + s + "\"") : ""), type);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.notice(s, fPrintOnly, id)
|
||||
*
|
||||
* notice() is like println() but implies a need for user notification, which means calling log() isn't good enough,
|
||||
* so we alert() as well; however, if Component.println() is overridden, Component.notice will be replaced with the same
|
||||
* override, on the assumption that the override is taking care of all user notifications.
|
||||
*
|
||||
* @param {string} s is the message text
|
||||
* @param {boolean} [fPrintOnly]
|
||||
* @param {string} [id] is the caller's ID, if any
|
||||
*/
|
||||
Component.notice = function(s, fPrintOnly, id)
|
||||
{
|
||||
if (DEBUG) {
|
||||
Component.println(s, "notice", id);
|
||||
}
|
||||
if (!fPrintOnly) web.alertUser(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.warning(s)
|
||||
*
|
||||
* @param {string} s describes the warning
|
||||
*/
|
||||
Component.warning = function(s)
|
||||
{
|
||||
if (DEBUG) {
|
||||
Component.println(s, "warning");
|
||||
}
|
||||
web.alertUser(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.error(s)
|
||||
*
|
||||
* @param {string} s describes the error; an alert() is displayed as well
|
||||
*/
|
||||
Component.error = function(s)
|
||||
{
|
||||
if (DEBUG) {
|
||||
Component.println(s, "error");
|
||||
}
|
||||
web.alertUser(s);
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getComponents(idRelated)
|
||||
*
|
||||
* We could store components as properties of an 'all' object, using the component's ID,
|
||||
* and change this linear lookup into a property lookup, but some components may have no ID.
|
||||
*
|
||||
* @param {string} [idRelated] of related component
|
||||
* @return {Array} of components
|
||||
*/
|
||||
Component.getComponents = function(idRelated)
|
||||
{
|
||||
var i;
|
||||
var aComponents = [];
|
||||
/*
|
||||
* getComponentByID(id, idRelated)
|
||||
*
|
||||
* If idRelated is provided, we check it for a machine prefix, and use any
|
||||
* existing prefix to constrain matches to IDs with the same prefix, in order to
|
||||
* avoid matching components belonging to other machines.
|
||||
*/
|
||||
if (idRelated) {
|
||||
if ((i = idRelated.indexOf('.')) > 0)
|
||||
idRelated = idRelated.substr(0, i + 1);
|
||||
else
|
||||
idRelated = undefined;
|
||||
}
|
||||
for (i = 0; i < Component.all.length; i++) {
|
||||
var component = Component.all[i];
|
||||
if (!idRelated || !component.id.indexOf(idRelated)) {
|
||||
aComponents.push(component);
|
||||
}
|
||||
}
|
||||
return aComponents;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getComponentByID(id, idRelated)
|
||||
*
|
||||
* We could store components as properties of an 'all' object, using the component's ID,
|
||||
* and change this linear lookup into a property lookup, but some components may have no ID.
|
||||
*
|
||||
* @param {string} id of the desired component
|
||||
* @param {string} [idRelated] of related component
|
||||
* @return {Component|null}
|
||||
*/
|
||||
Component.getComponentByID = function(id, idRelated)
|
||||
{
|
||||
if (id !== undefined) {
|
||||
var i;
|
||||
/*
|
||||
* If idRelated is provided, we check it for a machine prefix, and use any
|
||||
* existing prefix to constrain matches to IDs with the same prefix, in order to
|
||||
* avoid matching components belonging to other machines.
|
||||
*/
|
||||
if (idRelated && (i = idRelated.indexOf('.')) > 0) {
|
||||
id = idRelated.substr(0, i + 1) + id;
|
||||
}
|
||||
for (i = 0; i < Component.all.length; i++) {
|
||||
if (Component.all[i].id === id) {
|
||||
return Component.all[i];
|
||||
}
|
||||
}
|
||||
Component.log('Component.getComponentByID("' + id + '"): no component found', "warning");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getComponentByType(sType, idRelated, componentPrev)
|
||||
*
|
||||
* @param {string} sType of the desired component
|
||||
* @param {string} [idRelated] of related component
|
||||
* @param {Component} [componentPrev] of previously returned component, if any
|
||||
* @return {Component|null}
|
||||
*/
|
||||
Component.getComponentByType = function(sType, idRelated, componentPrev)
|
||||
{
|
||||
if (sType !== undefined) {
|
||||
var i;
|
||||
/*
|
||||
* If idRelated is provided, we check it for a machine prefix, and use any
|
||||
* existing prefix to constrain matches to IDs with the same prefix, in order to
|
||||
* avoid matching components belonging to other machines.
|
||||
*/
|
||||
if (idRelated) {
|
||||
if ((i = idRelated.indexOf('.')) > 0) {
|
||||
idRelated = idRelated.substr(0, i + 1);
|
||||
} else {
|
||||
idRelated = undefined;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < Component.all.length; i++) {
|
||||
if (componentPrev) {
|
||||
if (componentPrev == Component.all[i]) componentPrev = null;
|
||||
continue;
|
||||
}
|
||||
if (sType == Component.all[i].type && (!idRelated || !Component.all[i].id.indexOf(idRelated))) {
|
||||
return Component.all[i];
|
||||
}
|
||||
}
|
||||
Component.log('Component.getComponentByType("' + sType + '"): no component found', "warning");
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getComponentParms(element)
|
||||
*
|
||||
* @param {Object} element from the DOM
|
||||
*/
|
||||
Component.getComponentParms = function(element)
|
||||
{
|
||||
var parms = null,
|
||||
sParms = element.getAttribute("data-value");
|
||||
if (sParms) {
|
||||
try {
|
||||
parms = eval("({" + sParms + "})"); // jshint ignore:line
|
||||
/*
|
||||
* We can no longer invoke removeAttribute() because some components (eg, Panel) need
|
||||
* to run their initXXX() code more than once, to avoid initialization-order dependencies.
|
||||
*
|
||||
* if (!DEBUG) {
|
||||
* element.removeAttribute("data-value");
|
||||
* }
|
||||
*/
|
||||
} catch (e) {
|
||||
Component.error(e.message + " (" + sParms + ")");
|
||||
}
|
||||
}
|
||||
return parms;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.bindExternalControl(component, sControl, sBinding, sType)
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {string} sControl
|
||||
* @param {string} sBinding
|
||||
* @param {string} [sType] is the external component type
|
||||
*/
|
||||
Component.bindExternalControl = function(component, sControl, sBinding, sType)
|
||||
{
|
||||
if (sControl) {
|
||||
if (sType === undefined) sType = "Panel";
|
||||
var target = Component.getComponentByType(sType, component.id);
|
||||
if (target) {
|
||||
var eBinding = target.bindings[sControl];
|
||||
if (eBinding) {
|
||||
component.setBinding(null, null, sBinding, eBinding);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.bindComponentControls(component, element, sAppClass)
|
||||
*
|
||||
* @param {Component} component
|
||||
* @param {Object} element from the DOM
|
||||
* @param {string} sAppClass
|
||||
*/
|
||||
Component.bindComponentControls = function(component, element, sAppClass)
|
||||
{
|
||||
var iControl, aeControls;
|
||||
aeControls = Component.getElementsByClass(element.parentNode, sAppClass + "-control");
|
||||
for (iControl = 0; iControl < aeControls.length; iControl++) {
|
||||
var iNode, aeChildNodes;
|
||||
aeChildNodes = aeControls[iControl].childNodes;
|
||||
for (iNode = 0; iNode < aeChildNodes.length; iNode++) {
|
||||
var control = aeChildNodes[iNode];
|
||||
if (control.nodeType !== window.document.ELEMENT_NODE) {
|
||||
continue;
|
||||
}
|
||||
var sClass = control.getAttribute("class");
|
||||
if (!sClass) continue;
|
||||
var iClass, aClasses;
|
||||
aClasses = sClass.split(" ");
|
||||
for (iClass = 0; iClass < aClasses.length; iClass++) {
|
||||
var parms;
|
||||
sClass = aClasses[iClass];
|
||||
switch (sClass) {
|
||||
case sAppClass + "-input":
|
||||
case sAppClass + "-output":
|
||||
parms = Component.getComponentParms(control);
|
||||
if (parms && parms['binding']) {
|
||||
component.setBinding(sClass, parms['type'], parms['binding'], control);
|
||||
} else {
|
||||
Component.log('Component.bindComponentControls("' + component.toString() + '"): no binding info for ' + parms['type'], "warning");
|
||||
}
|
||||
aClasses = [];
|
||||
break;
|
||||
default:
|
||||
// Component.log("Component.bindComponentControls(" + component.toString() + "): unrecognized control class \"" + sClass + "\"", "warning");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Component.getElementsByClass(element, sClass, sObjClass)
|
||||
*
|
||||
* This is a cross-browser helper function, since not all browser's support getElementsByClassName()
|
||||
*
|
||||
* TODO: This should probably be moved into weblib.js at some point, along with the control binding functions above,
|
||||
* to keep all the browser-related code together.
|
||||
*
|
||||
* @param {Object} element from the DOM
|
||||
* @param {string} sClass
|
||||
* @param {string} [sObjClass]
|
||||
* @return {Array|NodeList}
|
||||
*/
|
||||
Component.getElementsByClass = function(element, sClass, sObjClass)
|
||||
{
|
||||
if (sObjClass) sClass += '-' + sObjClass + "-object";
|
||||
/*
|
||||
* Use the browser's built-in getElementsByClassName() if it appears to be available
|
||||
* (for example, it's not available in IE8, but it should be available in IE9 and up)
|
||||
*/
|
||||
if (element.getElementsByClassName) {
|
||||
return element.getElementsByClassName(sClass);
|
||||
}
|
||||
var i, j, ae = [];
|
||||
var aeAll = element.getElementsByTagName("*");
|
||||
var re = new RegExp('(^| )' + sClass + '( |$)');
|
||||
for (i = 0, j = aeAll.length; i < j; i++) {
|
||||
if (re.test(aeAll[i].className)) {
|
||||
ae.push(aeAll[i]);
|
||||
}
|
||||
}
|
||||
if (!ae.length) {
|
||||
Component.log('no elements of class "' + sClass + '" found');
|
||||
}
|
||||
return ae;
|
||||
};
|
||||
|
||||
Component.prototype = {
|
||||
constructor: Component,
|
||||
/**
|
||||
* toString()
|
||||
*
|
||||
* @this {Component}
|
||||
* @return {string}
|
||||
*/
|
||||
toString: function() {
|
||||
return (this.name? this.name : (this.id || this.type));
|
||||
},
|
||||
/**
|
||||
* getMachineNum()
|
||||
*
|
||||
* @this {Component}
|
||||
* @return {number} unique machine number
|
||||
*/
|
||||
getMachineNum: function() {
|
||||
var nMachine = 1;
|
||||
if (this.idMachine) {
|
||||
var aDigits = this.idMachine.match(/\d+/);
|
||||
if (aDigits !== null)
|
||||
nMachine = parseInt(aDigits[0], 10);
|
||||
}
|
||||
return nMachine;
|
||||
},
|
||||
/**
|
||||
* setBinding(sHTMLClass, sHTMLType, sBinding, control)
|
||||
*
|
||||
* Component's setBinding() method is intended to be overridden by subclasses. The only
|
||||
* exception is the Panel component, which passes two special bindings ("clear" and "print")
|
||||
* back to us if no one else accepted them, so that we can redirect all println() requests
|
||||
* to those controls.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string|null} sHTMLClass is the class of the HTML control (eg, "input", "output")
|
||||
* @param {string|null} sHTMLType is the type of the HTML control (eg, "button", "list", "text", "submit", "textarea", "canvas")
|
||||
* @param {string} sBinding is the value of the 'binding' parameter stored in the HTML control's "data-value" attribute (eg, "reset")
|
||||
* @param {Object} control is the HTML control DOM object (eg, HTMLButtonElement)
|
||||
* @return {boolean} true if binding was successful, false if unrecognized binding request
|
||||
*/
|
||||
setBinding: function(sHTMLClass, sHTMLType, sBinding, control) {
|
||||
switch (sBinding) {
|
||||
case "clear":
|
||||
if (!this.bindings[sBinding]) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.onclick = (function(component) {
|
||||
return function() {
|
||||
if (component.bindings['print']) {
|
||||
component.bindings['print'].value = "";
|
||||
}
|
||||
};
|
||||
}(this));
|
||||
}
|
||||
return true;
|
||||
case "print":
|
||||
if (!this.bindings[sBinding]) {
|
||||
this.bindings[sBinding] = control;
|
||||
control.value = ""; // this was added for Firefox (Safari automatically clears the <textarea> on a page reload, but Firefox does not)
|
||||
Component.println = (function(control) {
|
||||
return function printControl(s, type) {
|
||||
s = (type !== undefined? (type + ": ") : "") + (s || "");
|
||||
if (!DEBUG) { // in non-DEBUG builds, prevent the <textarea> from getting too large, otherwise printing becomes slower and slower
|
||||
if (control.value.length > 8192) {
|
||||
control.value = control.value.substr(control.value.length - 4096);
|
||||
}
|
||||
}
|
||||
control.value += s + "\n";
|
||||
control.scrollTop = control.scrollHeight;
|
||||
if (DEBUG) console.log(s);
|
||||
};
|
||||
}(control));
|
||||
/*
|
||||
* Override Component.notice() with a replacement function that eliminates the web.alertUser() call
|
||||
*/
|
||||
Component.notice = function(s, fPrintOnly, id) {
|
||||
Component.println(s, "notice", id);
|
||||
};
|
||||
/*
|
||||
* HACK: Save this particular HTML element so that the Debugger can access it, too
|
||||
*/
|
||||
Component.controlPrint = control;
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
/*
|
||||
* Now that we're giving the Panel component multiple shots at binding its controls,
|
||||
* to relax initialization dependencies, we need to chill when unrecognized requests come in.
|
||||
*
|
||||
if (sHTMLClass == "input") {
|
||||
control.onclick = function() {
|
||||
Component.println("unsupported " + sHTMLType + ": " + sBinding);
|
||||
};
|
||||
}
|
||||
this.log("setBinding(\"" + sHTMLClass + "\",\"" + sHTMLType + "\",\"" + sBinding + "\"): unrecognized binding");
|
||||
*/
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* log(s, type)
|
||||
*
|
||||
* For diagnostic output only.
|
||||
*
|
||||
* WARNING: Even though this function's body is completely wrapped in DEBUG, that won't prevent the Closure Compiler
|
||||
* from including it, so all calls must still be prefixed with "if (DEBUG) ....". For this reason, the class method,
|
||||
* Component.log(), is preferred, because the compiler IS smart enough to remove those calls.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} [s] is the message text
|
||||
* @param {string} [type] is the message type
|
||||
*/
|
||||
log: function(s, type) {
|
||||
if (DEBUG) Component.log(s, type || this.id || this.type);
|
||||
},
|
||||
/**
|
||||
* println(s, type)
|
||||
*
|
||||
* For non-diagnostic output, which some components override in order to make their output visible in their own way.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} [s] is the message text
|
||||
* @param {string} [type] is the message type
|
||||
* @param {string} [id] is the caller's ID, if any
|
||||
*/
|
||||
println: function(s, type, id) {
|
||||
Component.println(s, type, id || this.id);
|
||||
},
|
||||
/**
|
||||
* status(s)
|
||||
*
|
||||
* status() is like println() but it also includes information about the component (ie, the component ID),
|
||||
* which is why there is no corresponding Component.status() function.
|
||||
*
|
||||
* @param {string} s is the message text
|
||||
*/
|
||||
status: function(s) {
|
||||
this.println(this.idComponent + ": " + s);
|
||||
},
|
||||
/**
|
||||
* notice(s, fPrintOnly)
|
||||
*
|
||||
* notice() is like println() but implies a need for user notification, which means calling log() isn't good enough, so we alert() as well;
|
||||
* however, if Component.println() is overridden, Component.notice will be replaced with the same override, on the assumption that the override
|
||||
* is taking care of user notification.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} s is the message text
|
||||
* @param {boolean} [fPrintOnly]
|
||||
* @param {string} [id] is the caller's ID, if any
|
||||
*/
|
||||
notice: function(s, fPrintOnly, id) {
|
||||
Component.notice(s, fPrintOnly, id || this.id);
|
||||
},
|
||||
/**
|
||||
* warning(s)
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} s describes the warning
|
||||
*/
|
||||
warning: function(s) {
|
||||
Component.warning(s);
|
||||
},
|
||||
/**
|
||||
* error(s)
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} s describes the error; an alert() is displayed as well
|
||||
*/
|
||||
error: function(s) {
|
||||
Component.error(s);
|
||||
},
|
||||
/**
|
||||
* setError(s)
|
||||
*
|
||||
* Set a fatal error condition
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {string} s describes a fatal error condition
|
||||
*/
|
||||
setError: function(s) {
|
||||
this.fError = true;
|
||||
this.notice("Fatal error: " + s);
|
||||
},
|
||||
/**
|
||||
* clearError()
|
||||
*
|
||||
* Clear any fatal error condition
|
||||
*
|
||||
* @this {Component}
|
||||
*/
|
||||
clearError: function() {
|
||||
this.fError = false;
|
||||
},
|
||||
/**
|
||||
* isError()
|
||||
*
|
||||
* Report any fatal error condition
|
||||
*
|
||||
* @this {Component}
|
||||
* @return {boolean} true if a fatal error condition exists, false if not
|
||||
*/
|
||||
isError: function() {
|
||||
if (this.fError) {
|
||||
this.println(this.toString() + " error");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
/**
|
||||
* isReady(fnReady)
|
||||
*
|
||||
* Return the "ready" state of the component; if the component is not ready, it will queue the optional
|
||||
* notification function, otherwise it will immediately call the notification function, if any, without queuing it.
|
||||
*
|
||||
* NOTE: Since only the Computer component actually cares about the "readiness" of other components, the so-called
|
||||
* "queue" of notification functions supports exactly one function. This keeps things nice and simple.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {function()} [fnReady]
|
||||
* @return {boolean} true if the component is in a "ready" state, false if not
|
||||
*/
|
||||
isReady: function(fnReady) {
|
||||
if (fnReady) {
|
||||
if (this.fReady) {
|
||||
fnReady();
|
||||
} else {
|
||||
if (DEBUG) this.log("NOT ready");
|
||||
this.fnReady = fnReady;
|
||||
}
|
||||
}
|
||||
return this.fReady;
|
||||
},
|
||||
/**
|
||||
* setReady(fReady)
|
||||
*
|
||||
* Set the "ready" state of the component to true, and call any queued notification functions.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} [fReady] is assumed to indicate "ready" unless EXPLICITLY set to false
|
||||
*/
|
||||
setReady: function(fReady) {
|
||||
if (!this.fError) {
|
||||
this.fReady = (fReady !== false);
|
||||
if (this.fReady) {
|
||||
if (DEBUG || this.name) this.log("ready");
|
||||
var fnReady = this.fnReady;
|
||||
this.fnReady = null;
|
||||
if (fnReady) fnReady();
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* isBusy(fCancel)
|
||||
*
|
||||
* Return the "busy" state of the component
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} [fCancel] is set to true to cancel a "busy" state
|
||||
* @return {boolean} true if "busy", false if not
|
||||
*/
|
||||
isBusy: function(fCancel) {
|
||||
if (this.fBusy) {
|
||||
if (fCancel) {
|
||||
this.fBusyCancel = true;
|
||||
} else if (fCancel === undefined) {
|
||||
this.println(this.toString() + " busy");
|
||||
}
|
||||
}
|
||||
return this.fBusy;
|
||||
},
|
||||
/**
|
||||
* setBusy(fBusy)
|
||||
*
|
||||
* Update the current busy state; if an fCancel request is pending, it will be honored now.
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} fBusy
|
||||
* @return {boolean}
|
||||
*/
|
||||
setBusy: function(fBusy) {
|
||||
if (this.fBusyCancel) {
|
||||
if (this.fBusy) {
|
||||
this.fBusy = false;
|
||||
}
|
||||
this.fBusyCancel = false;
|
||||
return false;
|
||||
}
|
||||
if (this.fError) {
|
||||
this.println(this.toString() + " error");
|
||||
return false;
|
||||
}
|
||||
this.fBusy = fBusy;
|
||||
return this.fBusy;
|
||||
},
|
||||
/**
|
||||
* powerUp(fSave)
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {Object|null} data
|
||||
* @param {boolean} [fRepower] is true if this is "repower" notification
|
||||
* @return {boolean} true if successful, false if failure
|
||||
*/
|
||||
powerUp: function(data, fRepower) {
|
||||
this.fPowered = true;
|
||||
return true;
|
||||
},
|
||||
/**
|
||||
* powerDown(fSave, fShutdown)
|
||||
*
|
||||
* @this {Component}
|
||||
* @param {boolean} fSave
|
||||
* @param {boolean} [fShutdown]
|
||||
* @return {Object|boolean} component state if fSave; otherwise, true if successful, false if failure
|
||||
*/
|
||||
powerDown: function(fSave, fShutdown) {
|
||||
if (fShutdown) this.fPowered = false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO: What was this work-around for? I forget....
|
||||
*/
|
||||
if (window && !window.document.ELEMENT_NODE) window.document.ELEMENT_NODE = 1;
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = Component;
|
||||
76
my_modules/shared/lib/defines.js
Normal file
76
my_modules/shared/lib/defines.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* @fileoverview Compile-time definitions used by C1Pjs and PCjs.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var APPNAME = ""; // this @define is overridden by the Closure Compiler with either "PCjs" or "C1Pjs"
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var APPVERSION = "1.0.0"; // this @define is overridden by the Closure Compiler with the version in package.json
|
||||
|
||||
/**
|
||||
* @define {string}
|
||||
*/
|
||||
var SITEHOST = "pcjs:8088"; // this @define is overridden by the Closure Compiler with "www.pcjs.org"
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*/
|
||||
var COMPILED = false; // this @define is overridden by the Closure Compiler (to true)
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*/
|
||||
var DEBUG = true; // this @define is overridden by the Closure Compiler (to false) to remove DEBUG-only code
|
||||
|
||||
/**
|
||||
* @define {boolean}
|
||||
*/
|
||||
var MAXDEBUG = false; // this @define is overridden by the Closure Compiler (to false) to remove MAXDEBUG-only code
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
global.window = false;
|
||||
global.APPNAME = APPNAME;
|
||||
global.APPVERSION = APPVERSION;
|
||||
global.SITEHOST = SITEHOST;
|
||||
global.COMPILED = COMPILED;
|
||||
global.DEBUG = DEBUG;
|
||||
global.MAXDEBUG = MAXDEBUG;
|
||||
/*
|
||||
* TODO: When we're "required" by Node, should we return anything via module.exports?
|
||||
*/
|
||||
}
|
||||
75
my_modules/shared/lib/diskapi.js
Normal file
75
my_modules/shared/lib/diskapi.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* @fileoverview Disk APIs, as defined by httpapi.js and consumed by disk.js
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Our "DiskIO API" looks like:
|
||||
*
|
||||
* http://www.pcjs.org/api/v1/disk?action=open&volume=*10mb.img&mode=demandrw&chs=c:h:s&machine=xxx&user=yyy
|
||||
*/
|
||||
var DiskAPI = {
|
||||
ENDPOINT: "/api/v1/disk",
|
||||
QUERY: {
|
||||
ACTION: "action", // value is one of DiskAPI.ACTION.*
|
||||
VOLUME: "volume", // value is path of a disk image
|
||||
MODE: "mode", // value is one of DiskAPI.MODE.*
|
||||
CHS: "chs", // value is cylinders:heads:sectors:bytes
|
||||
ADDR: "addr", // value is cylinder:head:sector:count
|
||||
MACHINE: "machine", // value is machine token
|
||||
USER: "user", // value is user ID
|
||||
DATA: "data" // value is data to be written
|
||||
},
|
||||
ACTION: {
|
||||
OPEN: "open",
|
||||
READ: "read",
|
||||
WRITE: "write",
|
||||
CLOSE: "close"
|
||||
},
|
||||
MODE: {
|
||||
LOCAL: "local", // this mode implies no API (at best, localStorage backing only)
|
||||
PRELOAD: "preload", // this mode implies use of the DumpAPI
|
||||
DEMANDRW: "demandrw",
|
||||
DEMANDRO: "demandro"
|
||||
},
|
||||
FAIL: {
|
||||
BADACTION: "invalid action",
|
||||
BADUSER: "invalid user",
|
||||
BADVOL: "invalid volume",
|
||||
OPENVOL: "unable to open volume",
|
||||
CREATEVOL: "unable to create volume",
|
||||
WRITEVOL: "unable to write volume",
|
||||
REVOKED: "access revoked"
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = DiskAPI;
|
||||
85
my_modules/shared/lib/dumpapi.js
Normal file
85
my_modules/shared/lib/dumpapi.js
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* @fileoverview Disk APIs, as defined by diskdump.js and consumed by disk.js
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Our "DiskDump API", such as it was, used to look like:
|
||||
*
|
||||
* http://jsmachines.net/bin/convdisk.php?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img
|
||||
*
|
||||
* To make it (a bit) more "REST-like", the above request now looks like:
|
||||
*
|
||||
* http://www.pcjs.org/api/v1/dump?disk=/disks/pc/dos/ibm/2.00/PCDOS200-DISK1.json&format=img
|
||||
*
|
||||
* Similarly, our "FileDump API" used to look like:
|
||||
*
|
||||
* http://jsmachines.net/bin/convrom.php?rom=/devices/pc/bios/5150/1981-04-24.rom&format=json
|
||||
*
|
||||
* and that request now looks like:
|
||||
*
|
||||
* http://www.pcjs.org/api/v1/dump?file=/devices/pc/bios/5150/1981-04-24.rom&format=json
|
||||
*
|
||||
* I don't think it makes sense to avoid "query" parameters, because blending the path of a disk image with the
|
||||
* the rest of the URL would be (a) confusing, and (b) more work to parse.
|
||||
*/
|
||||
var DumpAPI = {
|
||||
ENDPOINT: "/api/v1/dump",
|
||||
QUERY: {
|
||||
DIR: "dir", // value is path of a directory (DiskDump only)
|
||||
DISK: "disk", // value is path of a disk image (DiskDump only)
|
||||
FILE: "file", // value is path of a ROM image file (FileDump only)
|
||||
IMG: "img", // alias for DISK
|
||||
PATH: "path", // value is path of a one or more files (DiskDump only)
|
||||
FORMAT: "format", // value is one of FORMAT values below
|
||||
COMMENTS: "comments", // value is either "true" or "false"
|
||||
DECIMAL: "decimal", // value is either "true" to force all numbers to decimal, "false" or undefined otherwise
|
||||
MBHD: "mbhd" // value is hard disk size in Mb (formerly "mbsize") (DiskDump only)
|
||||
},
|
||||
FORMAT: {
|
||||
JSON: "json", // default
|
||||
DATA: "data", // same as "json", but built without JSON.stringify() (DiskDump only)
|
||||
HEX: "hex", // deprecated
|
||||
BYTES: "bytes", // displays data as hex bytes; normally used only when comments are enabled
|
||||
IMG: "img", // returns the raw disk data (ie, using a Buffer object) (DiskDump only)
|
||||
ROM: "rom" // returns the raw file data (ie, using a Buffer object) (FileDump only)
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Because we use an overloaded API endpoint (ie, one that's shared with the FileDump module), we must
|
||||
* also provide a list of commands which, when combined with the endpoint, define a unique request.
|
||||
*/
|
||||
DumpAPI.asDiskCommands = [DumpAPI.QUERY.DIR, DumpAPI.QUERY.DISK, DumpAPI.QUERY.PATH];
|
||||
DumpAPI.asFileCommands = [DumpAPI.QUERY.FILE];
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = DumpAPI;
|
||||
372
my_modules/shared/lib/embed.js
Normal file
372
my_modules/shared/lib/embed.js
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
/**
|
||||
* @fileoverview C1Pjs and PCjs embedding functionality.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* @suppress {missingProperties}
|
||||
* Created 2012-Aug-28
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global window: true, XSLTProcessor: false, web: true, Component: true, APPNAME: false, APPVERSION: false, DEBUG: true */
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var Component;
|
||||
var str = require("./strlib");
|
||||
var web = require("./weblib");
|
||||
}
|
||||
|
||||
/**
|
||||
* loadXML(sFile, idMachine, sStateFile, fResolve)
|
||||
*
|
||||
* This is the preferred way to load all XML and XSL files. It uses loadResource()
|
||||
* to load them as strings, which parseXML() can massage before parsing/transforming them.
|
||||
*
|
||||
* For example, since I've been unable to get the XSLT document() function to work inside any
|
||||
* XSL document loaded by JavaScript's XSLT processor, that has prevented me from dynamically
|
||||
* loading any XML machine file that uses the "ref" attribute to refer to and incorporate
|
||||
* another XML document.
|
||||
*
|
||||
* To solve that, I've added an fResolve parameter that tells parseXML() to fetch any
|
||||
* referenced documents ITSELF and insert them into the XML string prior to parsing, instead
|
||||
* of relying on the XSLT template to pull them in. That fetching is handled by resolveRefs(),
|
||||
* which iterates over the XML until all "refs" have been resolved (including any nested
|
||||
* references).
|
||||
*
|
||||
* Also, XSL files with a <!DOCTYPE [...]> cause MSIE's Microsoft.XMLDOM.loadXML() function
|
||||
* to choke, so I strip that out prior to parsing as well.
|
||||
*
|
||||
* TODO: Figure out why the XSLT document() function works great when the web browser loads an
|
||||
* XML file (and the associated XSL file) itself, but does not work when loading documents via
|
||||
* JavaScript XSLT support. Is it broken, is it a security issue, or am I just calling it wrong?
|
||||
*
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} [idMachine]
|
||||
* @param {string} [sStateFile]
|
||||
* @param {boolean} [fResolve] is true to resolve any "ref" attributes
|
||||
* @return {Array} where [0] contains the unparsed XML string data, and [1] contains a parsed XML object
|
||||
*/
|
||||
function loadXML(sXMLFile, idMachine, sStateFile, fResolve)
|
||||
{
|
||||
var response = web.loadResource(sXMLFile);
|
||||
if (response[0]) {
|
||||
throw new Error(response[1]);
|
||||
}
|
||||
return parseXML(response[1], sXMLFile, idMachine, sStateFile, fResolve);
|
||||
}
|
||||
|
||||
/**
|
||||
* parseXML(sXML, idMachine, sStateFile, fResolve)
|
||||
*
|
||||
* Generates an XML document from an XML string. This function also provides a work-around for XSLT's
|
||||
* lack of support for the document() function (at least on some browsers), by replacing every reference
|
||||
* tag (ie, a tag with a "ref" attribute) with the contents of the referenced file.
|
||||
*
|
||||
* @param {string|null} sXML
|
||||
* @param {string|null} sXMLFile
|
||||
* @param {string} [idMachine]
|
||||
* @param {string} [sStateFile]
|
||||
* @param {boolean} [fResolve] is true to resolve any "ref" attributes; default is false
|
||||
* @return {Array} where [0] contains the unparsed XML string data, and [1] contains a parsed XML object
|
||||
*/
|
||||
function parseXML(sXML, sXMLFile, idMachine, sStateFile, fResolve)
|
||||
{
|
||||
var xmlDoc = null;
|
||||
if (sXML) {
|
||||
if (fResolve) {
|
||||
sXML = resolveRefs(sXML);
|
||||
}
|
||||
if (idMachine) {
|
||||
var sURL = sXMLFile;
|
||||
if (sURL && sURL.indexOf('/') < 0) sURL = window.location.pathname + sURL;
|
||||
sXML = sXML.replace(/(<machine[^>]*\sid=)(['"]).*?\2/, "$1$2" + idMachine + "$2" + (sStateFile? " state=$2" + sStateFile + "$2" : "") + (sURL? " url=$2" + sURL + "$2" : ""));
|
||||
}
|
||||
/*
|
||||
* If the resource we requested is not really an XML file (or the file didn't exist and the server simply returned
|
||||
* a message like "Cannot GET /configs/pc/machines/5150/cga/64kb/donkey/index.xml"), we'd like to display a more
|
||||
* meaningful message, because the XML DOM parsers will blithely return a document that contains nothing useful; eg:
|
||||
*
|
||||
* This page contains the following errors:error on line 1 at column 1:
|
||||
* Document is empty Below is a rendering of the page up to the first error.
|
||||
*
|
||||
* Supposedly, the IE XML DOM parser will throw an exception, but I haven't tested that, and unless all other
|
||||
* browsers do that, that's not helpful.
|
||||
*
|
||||
* The best I can do at this stage (assuming web.loadResource() didn't drop any error information on the floor)
|
||||
* is verify that the requested resource "looks like" valid XML (in other words, it begins with a "<").
|
||||
*/
|
||||
if (sXML.indexOf("<") === 0) {
|
||||
if (window.ActiveXObject || "ActiveXObject" in window) { // second test is required for IE11 on Windows 8.1
|
||||
/*
|
||||
* Another hack for MSIE, which fails to properly load XSL documents containing a <!DOCTYPE [...]> tag.
|
||||
*/
|
||||
if (!fResolve) {
|
||||
sXML = sXML.replace(/<!DOCTYPE(.|[\r\n])*\]>\s*/g, "");
|
||||
}
|
||||
xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM");
|
||||
xmlDoc.async = false;
|
||||
xmlDoc.loadXML(sXML);
|
||||
} else {
|
||||
xmlDoc = (new window.DOMParser()).parseFromString(sXML, "text/xml");
|
||||
}
|
||||
} else {
|
||||
throw new Error("unrecognized XML: " + (sXML.length > 255? sXML.substr(0, 255) + "..." : sXML));
|
||||
}
|
||||
}
|
||||
return [sXML, xmlDoc];
|
||||
}
|
||||
|
||||
/**
|
||||
* resolvesRefs(sXML)
|
||||
*
|
||||
* Replaces every tag with a "ref" attribute with the contents of the corresponding file.
|
||||
*
|
||||
* TODO: Fix some of the limitations of this code, such as: 1) requiring the "ref" attribute
|
||||
* to appear as the tag's first attribute, 2) requiring the "ref" attribute to be double-quoted,
|
||||
* and 3) requiring the "ref" tag to be self-closing.
|
||||
*
|
||||
* @param {string} sXML
|
||||
* @returns {string} with all tags with "ref" attributes replaced with the referenced file instead
|
||||
*/
|
||||
function resolveRefs(sXML)
|
||||
{
|
||||
var matchRef, sError;
|
||||
var reRef = /<([a-z]+)\s+ref="(.*?)"(.*?)\/>/g;
|
||||
while ((matchRef = reRef.exec(sXML))) {
|
||||
var sRefFile = matchRef[2];
|
||||
var response = web.loadResource(sRefFile);
|
||||
var sXMLRef = response[1];
|
||||
if (response[0] || !sXMLRef) {
|
||||
sError = "unable to resolve XML reference: " + matchRef[0] + " (" + response[0] + ")";
|
||||
Component.log(sError);
|
||||
throw new Error(sError);
|
||||
}
|
||||
/*
|
||||
* If there are additional attributes in the "referring" XML tag, we want to insert them
|
||||
* into the "referred" XML tag; attributes that don't exist in the referred tag should be
|
||||
* appended, and attributes that DO exist should be overwritten.
|
||||
*/
|
||||
var sRefAttrs = matchRef[3];
|
||||
if (sRefAttrs) {
|
||||
var aXMLRefTag = sXMLRef.match(new RegExp("<" + matchRef[1] + "[^>]*>"));
|
||||
if (aXMLRefTag) {
|
||||
var sXMLNewTag = aXMLRefTag[0];
|
||||
/*
|
||||
* Iterate over all the attributes in the "referring" XML tag (sRefAttrs)
|
||||
*/
|
||||
var matchAttr;
|
||||
var reAttr = /( [a-z]+=)(['"])(.*?)\2/g;
|
||||
while ((matchAttr = reAttr.exec(sRefAttrs))) {
|
||||
if (sXMLNewTag.indexOf(matchAttr[1]) < 0) {
|
||||
/*
|
||||
* This is the append case
|
||||
*/
|
||||
sXMLNewTag = sXMLNewTag.replace(">", matchAttr[0] + ">");
|
||||
} else {
|
||||
/*
|
||||
* This is the overwrite case
|
||||
*/
|
||||
sXMLNewTag = sXMLNewTag.replace(new RegExp(matchAttr[1] + "(['\"])(.*?)\\1"), matchAttr[0]);
|
||||
}
|
||||
}
|
||||
if (aXMLRefTag[0] != sXMLNewTag) {
|
||||
sXMLRef = sXMLRef.replace(aXMLRefTag[0], sXMLNewTag);
|
||||
}
|
||||
} else {
|
||||
sError = "missing <" + matchRef[1] + "> in " + sRefFile;
|
||||
Component.log(sError);
|
||||
throw new Error(sError);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Apparently when a Windows Azure server delivers one of my XML files, it may modify the first line:
|
||||
*
|
||||
* <?xml version="1.0" encoding="UTF-8"?>\n
|
||||
*
|
||||
* I didn't determine exactly what it was doing at this point (probably just changing the \n to \r\n),
|
||||
* but in any case, relaxing the following replace() solved it.
|
||||
*/
|
||||
sXMLRef = sXMLRef.replace(/<\?xml[^>]*>[\r\n]*/, "");
|
||||
|
||||
sXML = sXML.replace(matchRef[0], sXMLRef);
|
||||
|
||||
reRef.lastIndex = 0; // reset lastIndex, since we just modified the string that reRef is iterating over
|
||||
}
|
||||
return sXML;
|
||||
}
|
||||
|
||||
/**
|
||||
* embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile)
|
||||
*
|
||||
* This allows to you embed a machine on a web page, by transforming the machine XML into HTML.
|
||||
*
|
||||
* @param {string} sName is the app name (eg, "PCjs" or "C1Pjs")
|
||||
* @param {string} sVersion is the app version (eg, "1.12.1")
|
||||
* @param {string} idElement
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} [sXSLFile]
|
||||
* @param {string} [sStateFile]
|
||||
* @return {string} containing the complete XML string data, or an error if the XML could not be parsed
|
||||
*/
|
||||
function embedMachine(sName, sVersion, idElement, sXMLFile, sXSLFile, sStateFile)
|
||||
{
|
||||
var sXML = "", sError = "", eMachine = null;
|
||||
|
||||
try {
|
||||
eMachine = window.document.getElementById(idElement);
|
||||
if (eMachine) {
|
||||
var sAppClass = sName.toLowerCase(); // eg, "pcjs" or "c1pjs"
|
||||
if (!sXSLFile) {
|
||||
if (DEBUG && sVersion == "1.0.0") {
|
||||
sXSLFile = "/my_modules/" + sAppClass + "-client/templates/components.xsl";
|
||||
} else {
|
||||
sXSLFile = "/versions/" + sAppClass + "/" + sVersion + "/components.xsl";
|
||||
}
|
||||
}
|
||||
var aXML = (sXMLFile.substr(0, 1) == "<" ? parseXML(sXMLFile, null, idElement, sStateFile, false) : loadXML(sXMLFile, idElement, sStateFile, true));
|
||||
sXML = aXML[0];
|
||||
var xml = aXML[1];
|
||||
if (xml) {
|
||||
aXML = loadXML(sXSLFile);
|
||||
var xsl = aXML[1];
|
||||
if (xsl) {
|
||||
/*
|
||||
* The <machine> template in components.xsl now generates a "machine div" that makes
|
||||
* the div we required the caller of embedMachine() to provide redundant, so instead
|
||||
* of appending this fragment to the caller's node, we REPLACE the caller's node.
|
||||
* This works only because because we ALSO inject the caller's "machine div" ID into
|
||||
* the fragment's ID during parseXML().
|
||||
*
|
||||
* eMachine.innerHTML = sFragment;
|
||||
*
|
||||
* Also, if the transform function fails, make sure you're using the appropriate
|
||||
* "components.xsl" and not a "machine.xsl", because the latter will not produce valid
|
||||
* embeddable HTML (and is the most common cause of failure at this final stage).
|
||||
*/
|
||||
if (window.ActiveXObject || "ActiveXObject" in window) { // second test is required for IE11 on Windows 8.1
|
||||
var sFragment = xml['transformNode'](xsl);
|
||||
if (sFragment) {
|
||||
eMachine.outerHTML = sFragment;
|
||||
} else {
|
||||
Component.log(sError = "transformNodeToObject failed");
|
||||
}
|
||||
}
|
||||
else if (window.document.implementation && window.document.implementation.createDocument) {
|
||||
var xsltProcessor = new XSLTProcessor();
|
||||
xsltProcessor['importStylesheet'](xsl);
|
||||
var eFragment = xsltProcessor['transformToFragment'](xml, window.document);
|
||||
if (eFragment) {
|
||||
eMachine.parentNode.replaceChild(eFragment, eMachine);
|
||||
} else {
|
||||
Component.log(sError = "transformToFragment failed");
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Perhaps I should have performed this test at the outset; on the other hand, I'm
|
||||
* not aware of any browsers don't support one or both of the above XSLT transformation
|
||||
* methods, so treat this as a bug.
|
||||
*/
|
||||
Component.log(sError = "unable to transform XML: unsupported browser");
|
||||
}
|
||||
} else {
|
||||
Component.log(sError = "failed to load XSL file: " + sXSLFile);
|
||||
}
|
||||
} else {
|
||||
Component.log(sError = "failed to load XML file: " + sXMLFile);
|
||||
}
|
||||
} else {
|
||||
Component.log(sError = "failed to find machine element: " + idElement);
|
||||
}
|
||||
} catch(e) {
|
||||
sError = e.message;
|
||||
}
|
||||
|
||||
if (sError && eMachine) {
|
||||
/*
|
||||
* Our MarkOut module (in convertMDMachineLinks()) creates machine containers that look like this:
|
||||
*
|
||||
* <div id="' + sMachineID + '" class="machine-placeholder"><p>Embedded PC</p><p class="machine-warning"></p></div>
|
||||
*
|
||||
* with the "machine-warning" paragraph pre-populated with a warning message that the user will
|
||||
* see if nothing at all happens. But hopefully, in the normal case (and especially the error case),
|
||||
* *something* will have happened.
|
||||
*
|
||||
* Note that it is the HTMLOut module (in processMachines()) that ultimately decides which scripts to
|
||||
* include and then generates the embedPC() and/or embedC1P() calls.
|
||||
*/
|
||||
var aeError = Component.getElementsByClass(eMachine, "machine-warning");
|
||||
if (aeError[0]) {
|
||||
aeError[0].innerHTML = "Error: " + str.escapeHTML(sError);
|
||||
}
|
||||
}
|
||||
|
||||
return sError || sXML;
|
||||
}
|
||||
|
||||
/**
|
||||
* embedC1P(idElement, sXMLFile, sXSLFile)
|
||||
*
|
||||
* @param {string} idElement
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} [sXSLFile]
|
||||
* @return {string} XML string data or error message
|
||||
*/
|
||||
function embedC1P(idElement, sXMLFile, sXSLFile)
|
||||
{
|
||||
return embedMachine("C1Pjs", APPVERSION, idElement, sXMLFile, sXSLFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* embedPC(idElement, sXMLFile, sXSLFile, sStateFile)
|
||||
*
|
||||
* @param {string} idElement
|
||||
* @param {string} sXMLFile
|
||||
* @param {string} [sXSLFile]
|
||||
* @param {string} [sStateFile]
|
||||
* @return {string} XML string data or error message
|
||||
*/
|
||||
function embedPC(idElement, sXMLFile, sXSLFile, sStateFile)
|
||||
{
|
||||
return embedMachine("PCjs", APPVERSION, idElement, sXMLFile, sXSLFile, sStateFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the Closure Compiler from renaming functions we want to export, by adding them
|
||||
* as (named) properties of a global object.
|
||||
*/
|
||||
if (APPNAME == "PCjs") {
|
||||
window['embedPC'] = embedPC;
|
||||
}
|
||||
|
||||
if (APPNAME == "C1Pjs") {
|
||||
window['embedC1P'] = embedC1P;
|
||||
}
|
||||
|
||||
window['enableEvents'] = web.enablePageEvents;
|
||||
window['sendEvent'] = web.sendPageEvent;
|
||||
39
my_modules/shared/lib/externs.js
Normal file
39
my_modules/shared/lib/externs.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* @fileoverview Externs used by PCjs and C1Pjs (for the Closure Compiler)
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2012-Dec-04
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Unless we declare "module", even code like "if (typeof module === 'undefined') ..." is disallowed by the compiler.
|
||||
*/
|
||||
var module;
|
||||
var webkitAudioContext;
|
||||
385
my_modules/shared/lib/netlib.js
Normal file
385
my_modules/shared/lib/netlib.js
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
/**
|
||||
* @fileoverview Net-related functions
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
|
||||
* @version 1.0
|
||||
* Created 2014-03-16
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global Buffer: false */
|
||||
|
||||
var net = {};
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
var sServerRoot;
|
||||
var fs = require("fs");
|
||||
var http = require("http");
|
||||
var path = require("path");
|
||||
var url = require("url");
|
||||
var str = require("./strlib");
|
||||
}
|
||||
|
||||
/*
|
||||
* The following are (super-secret) commands that can be added to the URL to enable special features.
|
||||
*
|
||||
* Our super-secret command processor is affectionately call Gort, and while Gort doesn't understand commands
|
||||
* like "Klaatu barada nikto", it does understand commands like "debug" and "rebuild"; eg:
|
||||
*
|
||||
* http://www.pcjs.org/?gort=debug
|
||||
*
|
||||
* hasParm() detects the presence of the specified command, and propagateParms() is a URL filter that ensures
|
||||
* any commands listed in asPropagate are passed through to all other URLs on the same page; using any of these
|
||||
* commands also forces the page to be rebuilt and not cached (since we would never want a cached "index.html" to
|
||||
* contain/expose any of these commands).
|
||||
*/
|
||||
net.GORT_COMMAND = "gort";
|
||||
net.GORT_DEBUG = "debug"; // use this to force uncompiled JavaScript even on a Release server
|
||||
net.GORT_NODEBUG = "nodebug"; // use this to force uncompiled JavaScript but with DEBUG code disabled
|
||||
net.GORT_RELEASE = "release"; // use this to force the use of compiled JavaScript even on a Debug server
|
||||
net.GORT_REBUILD = "rebuild"; // use this to force the "index.html" in the current directory to be rebuilt
|
||||
|
||||
net.REVEAL_COMMAND = "reveal";
|
||||
net.REVEAL_PDFS = "pdfs";
|
||||
|
||||
/*
|
||||
* This is a list of the URL parameters that propagateParms() will propagate from the requester's URL to
|
||||
* other URLs provided by the requester.
|
||||
*/
|
||||
var asPropagate = [net.GORT_COMMAND, "autostart"];
|
||||
|
||||
/**
|
||||
* hasParm(sParm, sValue, req)
|
||||
*
|
||||
* @param {string} sParm
|
||||
* @param {string|null} sValue (pass null to check for the presence of ANY sParm)
|
||||
* @param {Object} [req] is the web server's (ie, Express) request object, if any
|
||||
* @return {boolean} true if the request Object contains the specified parameter/value, false if not
|
||||
*
|
||||
* TODO: Consider whether sParm === null should check for the presence of ANY parameter in asPropagate.
|
||||
*/
|
||||
net.hasParm = function(sParm, sValue, req)
|
||||
{
|
||||
return (req && req.query && req.query[sParm] && (!sValue || req.query[sParm] == sValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* propagateParms(sURL, req)
|
||||
*
|
||||
* Propagates any "special" query parameters (as listed in asPropagate) from the given
|
||||
* request object (req) to the given URL (sURL).
|
||||
*
|
||||
* We do not modify an sURL that already contains a '?' OR that begins with a protocol
|
||||
* (eg, http:, mailto:, etc), in order to keep this function simple, since it's only for
|
||||
* debugging purposes anyway. I also considered blowing off any URLs with a '#' for the
|
||||
* same reason, since any hash string must follow any query parameters, but stripping
|
||||
* and re-appending the hash string is pretty trivial, so we do handle that.
|
||||
*
|
||||
* TODO: Make propagateParms() more general-purpose (eg, capable of detecting any URL
|
||||
* to the same site, and capable of merging any of our "special" query parameters with any
|
||||
* existing query parameters.
|
||||
*
|
||||
* @param {string|null} sURL
|
||||
* @param {Object} [req] is the web server's (ie, Express) request object, if any
|
||||
* @return {string} massaged sURL
|
||||
*/
|
||||
net.propagateParms = function(sURL, req)
|
||||
{
|
||||
if (sURL !== null && sURL.indexOf('?') < 0) {
|
||||
var i;
|
||||
var sHash = "";
|
||||
if ((i = sURL.indexOf('#')) >= 0) {
|
||||
sHash = sURL.substr(i);
|
||||
sURL = sURL.substr(0, i);
|
||||
}
|
||||
var match = sURL.match(/^([a-z])+:(.*)/);
|
||||
if (!match && req && req.query) {
|
||||
for (i = 0; i < asPropagate.length; i++) {
|
||||
var sQuery = asPropagate[i];
|
||||
var sValue;
|
||||
if ((sValue = req.query[sQuery])) {
|
||||
var sParm = (sURL.indexOf('?') < 0? '?' : '&');
|
||||
sParm += sQuery + '=';
|
||||
if (sURL.indexOf(sParm) < 0) sURL += sParm + encodeURIComponent(sValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
sURL += sHash;
|
||||
}
|
||||
return sURL;
|
||||
};
|
||||
|
||||
/**
|
||||
* encodeURL(sURL, req, fDebug)
|
||||
*
|
||||
* Used to encodes any URLs presented on the current page, using this 3-step process:
|
||||
*
|
||||
* 1) Replace any backslashes with slashes, in case the URL was derived from a file system path
|
||||
* 2) Remap links that begin with "static/" to the corresponding URL at "http://static.pcjs.org/"
|
||||
* 3) Massage the result with net.propagateParms(), so that any special parameters are passed along
|
||||
* 4) Transform any "htmlspecialchars" into the corresponding entities, to help ensure proper validation
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {Object} req is the web server's (ie, Express) request object, if any
|
||||
* @param {boolean} [fDebug]
|
||||
* @return {string} encoded URL
|
||||
*/
|
||||
net.encodeURL = function(sURL, req, fDebug)
|
||||
{
|
||||
if (sURL) {
|
||||
sURL = sURL.replace(/\\/g, '/');
|
||||
if (!fDebug) {
|
||||
if (sURL.match(/^[^:]*static/)) {
|
||||
sURL = "http://static.pcjs.org" + path.join(req.path, sURL).replace("/static/", "/");
|
||||
}
|
||||
}
|
||||
return net.propagateParms(encodeURI(sURL), req);
|
||||
}
|
||||
return sURL;
|
||||
};
|
||||
|
||||
/**
|
||||
* isRemote(sPath)
|
||||
*
|
||||
* @param {string} sPath
|
||||
* @return {boolean} true if sPath is a (supported) remote path, false if not
|
||||
*
|
||||
* TODO: Add support for FTP? HTTPS? Anything else?
|
||||
*/
|
||||
net.isRemote = function(sPath)
|
||||
{
|
||||
return (sPath.indexOf("http:") === 0);
|
||||
};
|
||||
|
||||
/**
|
||||
* getStat(sURL, done)
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {function(Error,Object)} done
|
||||
*/
|
||||
net.getStat = function(sURL, done)
|
||||
{
|
||||
var options = url.parse(sURL);
|
||||
options.method = "HEAD";
|
||||
options.path = options.pathname; // TODO: Determine the necessity of aliasing this
|
||||
var req = http.request(options, function(res) {
|
||||
var err = null;
|
||||
var stat = null;
|
||||
// console.log(JSON.stringify(res.headers));
|
||||
if (res.statusCode == 200) {
|
||||
/*
|
||||
* Apparently Node lower-cases response headers (at least incoming headers, despite
|
||||
* lots of amusing whining by certain people in the Node community), which seems like
|
||||
* a good thing, because that means I can do two simple key look-ups.
|
||||
*/
|
||||
var sLength = res.headers['content-length'];
|
||||
var sModified = res.headers['last-modified'];
|
||||
stat = {
|
||||
size: sLength? parseInt(sLength, 10) : -1,
|
||||
mtime: sModified? new Date(sModified) : null,
|
||||
remote: true // an additional property we provide to indicate this is not your normal stats object
|
||||
};
|
||||
} else {
|
||||
err = new Error("unexpected response code: " + res.statusCode);
|
||||
}
|
||||
done(err, stat);
|
||||
});
|
||||
req.on('error', function(err) {
|
||||
done(err, null);
|
||||
});
|
||||
req.end();
|
||||
};
|
||||
|
||||
/**
|
||||
* getFile(sURL, sEncoding, done)
|
||||
*
|
||||
* @param {string} sURL is the source file
|
||||
* @param {string|null} sEncoding is the encoding to assume, if any
|
||||
* @param {function(Error,number,(string|Buffer))} done receives an Error, an HTTP status code, and a Buffer (if any)
|
||||
*
|
||||
* TODO: Add support for FTP? HTTPS? Anything else?
|
||||
*/
|
||||
net.getFile = function(sURL, sEncoding, done)
|
||||
{
|
||||
/*
|
||||
* Buffer objects are a fixed size, so my choices are: 1) call getStat() first, hope it returns
|
||||
* the true size, and then preallocate a buffer; or 2) create a new, larger buffer every time a new
|
||||
* chunk arrives. The latter seems best.
|
||||
*
|
||||
* However, if an encoding is given, we'll simply concatenate all the data into a String and return
|
||||
* that instead. Note that the incoming data is always a Buffer, but concatenation with a String
|
||||
* performs an implied "toString()" on the Buffer.
|
||||
*
|
||||
* WARNING: Even when an encoding is provided, we don't make any attempt to verify that the incoming
|
||||
* data matches that encoding.
|
||||
*/
|
||||
var sFile = "";
|
||||
var bufFile = null;
|
||||
http.get(sURL, function(res) {
|
||||
res.on('data', function(data) {
|
||||
if (sEncoding) {
|
||||
sFile += data;
|
||||
return;
|
||||
}
|
||||
if (!bufFile) {
|
||||
bufFile = data;
|
||||
return;
|
||||
}
|
||||
/*
|
||||
* We need to grow bufFile. I used to do this myself, using the "copy" method:
|
||||
*
|
||||
* buf.copy(targetBuffer, [targetStart], [sourceStart], [sourceEnd])
|
||||
*
|
||||
* which defaults to 0 for [targetStart] and [sourceStart], but the docs don't clearly
|
||||
* define the default value for [sourceEnd]. They say "buffer.length", but there is no
|
||||
* parameter here named "buffer". Let's hope that in the case of "bufFile.copy(buf)"
|
||||
* they meant "bufFile.length".
|
||||
*
|
||||
* However, it turns out this is moot, because there's a new kid in town: Buffer.concat().
|
||||
*
|
||||
* buf = new Buffer(bufFile.length + data.length);
|
||||
* bufFile.copy(buf);
|
||||
* data.copy(buf, bufFile.length);
|
||||
* bufFile = buf;
|
||||
*/
|
||||
bufFile = Buffer.concat([bufFile, data], bufFile.length + data.length);
|
||||
}).on('end', function() {
|
||||
/*
|
||||
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
|
||||
* in such cases, the file content will likely just be an HTML error page.
|
||||
*/
|
||||
if (res.statusCode < 400) {
|
||||
done(null, res.statusCode, sEncoding? sFile : bufFile);
|
||||
} else {
|
||||
done(new Error(sEncoding? sFile : bufFile), res.statusCode, null);
|
||||
}
|
||||
}).on('error', function(err) {
|
||||
done(err, res.statusCode, null);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* downloadFile(sURL, sFile, done)
|
||||
*
|
||||
* @param {string} sURL is the source file
|
||||
* @param {string} sFile is a fully-qualified target file
|
||||
* @param {function(Error,number)} done is a callback that receives an Error and a HTTP status code
|
||||
*/
|
||||
net.downloadFile = function(sURL, sFile, done)
|
||||
{
|
||||
var file = fs.createWriteStream(sFile);
|
||||
|
||||
/*
|
||||
* http.get() accepts a "url" string in lieu of an "options" object; it automatically builds
|
||||
* the latter from the former using url.parse(). This is good, because it relieves me from
|
||||
* building my own "options" object, and also from wondering why http functions expect "options"
|
||||
* to contain a "path" property, whereas url.parse() returns a "pathname" property.
|
||||
*
|
||||
* Either the documentation isn't quite right for url.parse() or http.request() (the big brother
|
||||
* of http.get), or one of those "options" properties is aliased to the other, or...?
|
||||
*/
|
||||
http.get(sURL, function(res) {
|
||||
res.on('data', function(data) {
|
||||
file.write(data);
|
||||
}).on('end', function() {
|
||||
file.end();
|
||||
/*
|
||||
* TODO: We should try to update the file's modification time to match the 'last-modified'
|
||||
* response header value, if any.
|
||||
*
|
||||
* TODO: Decide what to do when res.statusCode is actually an error code (eg, 404), because
|
||||
* in such cases, the file content will likely just be an HTML error page.
|
||||
*/
|
||||
done(null, res.statusCode);
|
||||
}).on('error', function(err) {
|
||||
done(err, res.statusCode);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* loadResource(sURL, fAsync, data, componentNotify, fnNotify, pNotify)
|
||||
*
|
||||
* Request the specified resource (sURL), and once the request is complete,
|
||||
* optionally call the specified method (fnNotify) of the specified component (componentNotify).
|
||||
*
|
||||
* TODO: Figure out how we can strongly type the fnNotify parameter, because the Closure Compiler has issues with:
|
||||
*
|
||||
* {function(this:Component, string, (string|null), number, (number|string|null|Object|Array|undefined))} [fnNotify]
|
||||
*
|
||||
* NOTE: This function is a mirror image of the weblib version, for server-side component testing within Node;
|
||||
* since it is NOT intended for production use, it may make liberal use of synchronous functions, warning messages, etc.
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {boolean} [fAsync] is true for an asynchronous request
|
||||
* @param {Object|null} [data] for a POST request (default is a GET request)
|
||||
* @param {Component} [componentNotify]
|
||||
* @param {function(...)} [fnNotify]
|
||||
* @param {number|string|null|Object|Array} [pNotify] optional fnNotify parameter
|
||||
* @return {Array} containing errorCode and responseText (empty array if async request)
|
||||
*/
|
||||
net.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNotify) {
|
||||
var nErrorCode = -1;
|
||||
var sResponse = null;
|
||||
if (net.isRemote(sURL)) {
|
||||
console.log('net.loadResource("' + sURL + '"): unimplemented');
|
||||
} else {
|
||||
if (!sServerRoot) {
|
||||
sServerRoot = path.join(path.dirname(fs.realpathSync(__filename)), "../../../");
|
||||
}
|
||||
var sFile = path.join(sServerRoot, sURL);
|
||||
if (fAsync) {
|
||||
fs.readFile(sFile, {encoding: "utf8"}, function(err, s) {
|
||||
/*
|
||||
* TODO: If err is set, is there an error code we should return (instead of -1)?
|
||||
*/
|
||||
if (!err) {
|
||||
sResponse = s;
|
||||
nErrorCode = 0;
|
||||
}
|
||||
if (componentNotify && fnNotify) fnNotify.call(componentNotify, str.getBaseName(sURL), sResponse, nErrorCode, pNotify);
|
||||
});
|
||||
return [];
|
||||
} else {
|
||||
try {
|
||||
sResponse = fs.readFileSync(sFile, {encoding: "utf8"});
|
||||
nErrorCode = 0;
|
||||
} catch(err) {
|
||||
/*
|
||||
* TODO: If err is set, is there an error code we should return (instead of -1)?
|
||||
*/
|
||||
console.log(err.message);
|
||||
}
|
||||
if (componentNotify && fnNotify) fnNotify.call(componentNotify, str.getBaseName(sURL), sResponse, nErrorCode, pNotify);
|
||||
}
|
||||
}
|
||||
return [nErrorCode, sResponse];
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = net;
|
||||
45
my_modules/shared/lib/nodebug.js
Normal file
45
my_modules/shared/lib/nodebug.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @fileoverview Compile-time definitions for non-DEBUG configurations.
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-Aug-22
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of PCjs, which is part of the JavaScript Machines Project (aka JSMachines)
|
||||
* at <http://jsmachines.net/> and <http://pcjs.org/>.
|
||||
*
|
||||
* PCjs is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCjs. If not,
|
||||
* see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some PCjs files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* PCjs program for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global DEBUG: true */
|
||||
|
||||
/*
|
||||
* In the compiled case, we rely on the Closure Compiler to override DEBUG, setting it to false,
|
||||
* so that all DEBUG-only code will be removed by the compiler.
|
||||
*
|
||||
* However, when we're in "development mode" and want to run uncompiled code without any DEBUG-only
|
||||
* code, we must arrange for this additional file, nodebug.js, to be loaded as early as possible,
|
||||
* which will then set DEBUG to false at runtime.
|
||||
*/
|
||||
DEBUG = false;
|
||||
90
my_modules/shared/lib/proclib.js
Normal file
90
my_modules/shared/lib/proclib.js
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* @fileoverview Process-related helper functions
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
|
||||
* @version 1.0
|
||||
* Created 2014-05-07
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var proc = {};
|
||||
|
||||
/**
|
||||
* getArgs()
|
||||
*
|
||||
* Processes command-line arguments. Arguments may be introduced by either
|
||||
* a double-hyphen (--) or a long dash (—), and argument values, if any, must be
|
||||
* separated by an "=" without any intervening whitespace. Arguments without
|
||||
* an explicit value default to true, and any argument appearing more than once
|
||||
* is automatically converted to an Array.
|
||||
*
|
||||
* Single-hyphen (-) arguments are allowed as well; they are treated as a series
|
||||
* of single-character arguments, each set to true, and any of these arguments
|
||||
* appearing more than once is discarded.
|
||||
*
|
||||
* @return {{argc:number, argv:{}}}
|
||||
*/
|
||||
proc.getArgs = function() {
|
||||
var argc = 0;
|
||||
var argv = {};
|
||||
for (var i = 2; i < process.argv.length; i++) {
|
||||
var j, sSep;
|
||||
var sArg = process.argv[i];
|
||||
if (!sArg.indexOf(sSep = "--") || !sArg.indexOf(sSep = "—")) {
|
||||
sArg = sArg.substr(sSep.length);
|
||||
var sValue = true;
|
||||
j = sArg.indexOf("=");
|
||||
if (j > 0) {
|
||||
sValue = sArg.substr(j+1);
|
||||
sArg = sArg.substr(0, j);
|
||||
sValue = (sValue == "true")? true : ((sValue == "false")? false : sValue);
|
||||
}
|
||||
if (argv[sArg] === undefined) {
|
||||
argc++;
|
||||
argv[sArg] = sValue;
|
||||
} else {
|
||||
// console.log("too many '" + sArg + "' arguments");
|
||||
if (typeof argv[sArg] == "string") {
|
||||
argv[sArg] = [argv[sArg]];
|
||||
}
|
||||
argv[sArg].push(sValue);
|
||||
}
|
||||
} else if (!sArg.indexOf("-")) {
|
||||
for (j = 1; j < sArg.length; j++) {
|
||||
var ch = sArg.charAt(j);
|
||||
if (argv[ch] === undefined) {
|
||||
argv[ch] = true;
|
||||
argc++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {argc: argc, argv: argv};
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = proc;
|
||||
53
my_modules/shared/lib/reportapi.js
Normal file
53
my_modules/shared/lib/reportapi.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @fileoverview Report API, as defined by httpapi.js
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-13
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReportAPI = {
|
||||
ENDPOINT: "/api/v1/report",
|
||||
QUERY: {
|
||||
APP: "app",
|
||||
VER: "ver",
|
||||
URL: "url",
|
||||
USER: "user",
|
||||
TYPE: "type",
|
||||
DATA: "data"
|
||||
},
|
||||
TYPE: {
|
||||
BUG: "bug"
|
||||
},
|
||||
RES: {
|
||||
OK: "Thank you"
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = ReportAPI;
|
||||
43
my_modules/shared/lib/sockets.js
Normal file
43
my_modules/shared/lib/sockets.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @fileoverview Experimental code included by HTMLOut() only if "--sockets"
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-Apr-29
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global io: false */
|
||||
|
||||
// connect to the socket server
|
||||
var socket = io.connect();
|
||||
|
||||
// if we get an "info" emit from the socket server then console.log the data we receive
|
||||
socket.on('info', function (data) {
|
||||
console.log(data);
|
||||
});
|
||||
323
my_modules/shared/lib/strlib.js
Normal file
323
my_modules/shared/lib/strlib.js
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
/**
|
||||
* @fileoverview String-related helper functions
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
|
||||
* @version 1.0
|
||||
* Created 2014-03-09
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var str = {};
|
||||
|
||||
/**
|
||||
* isValidInt(s, base)
|
||||
*
|
||||
* Since the built-in parseInt() function has the annoying feature of returning a partial value
|
||||
* when it encounters an invalid character (eg, parseInt("foo", 16) returns 0xf), use this function
|
||||
* to validate the entire string first.
|
||||
*
|
||||
* @param {string} s is the string representation of some number
|
||||
* @param {number} [base] is the radix of the number represented above (only 10 and 16 are supported)
|
||||
* @return {boolean} true if valid (or we're not sure because the base isn't recognized), false if invalid
|
||||
*/
|
||||
str.isValidInt = function(s, base)
|
||||
{
|
||||
if (!base || base == 10) return s.match(/^[0-9]+$/) !== null;
|
||||
if (base == 16) return s.match(/^[0-9a-f]+$/i) !== null;
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* parseInt(s, base)
|
||||
*
|
||||
* This is a wrapper around the built-in parseInt() function, which recognizes certain prefixes (eg,
|
||||
* '$' or "0x" for hex) and suffixes (eg, 'h' for hex or '.' for decimal), and then calls isValidInt()
|
||||
* to ensure we don't get partial values (see isValidInt() for details).
|
||||
*
|
||||
* @param {string} s is the string representation of some number
|
||||
* @param {number} [base] is the radix to assume (default is 16)
|
||||
* @return {number|undefined} corresponding value, or undefined if invalid
|
||||
*/
|
||||
str.parseInt = function(s, base)
|
||||
{
|
||||
var value;
|
||||
if (s) {
|
||||
if (!base) base = 16;
|
||||
if (s.charAt(0) == '$') {
|
||||
base = 16;
|
||||
s = s.substr(1);
|
||||
} else if (s.substr(0, 2) == "0x") {
|
||||
base = 16;
|
||||
s = s.substr(2);
|
||||
} else {
|
||||
var chSuffix = s.charAt(s.length-1).toLowerCase();
|
||||
if (chSuffix == 'h') {
|
||||
base = 16;
|
||||
chSuffix = null;
|
||||
}
|
||||
else if (chSuffix == '.') {
|
||||
base = 10;
|
||||
chSuffix = null;
|
||||
}
|
||||
if (chSuffix === null) s = s.substr(0, s.length-1);
|
||||
}
|
||||
var v;
|
||||
if (str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
|
||||
value = v;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* toHex(n, cch)
|
||||
*
|
||||
* Converts an integer to hex, with the specified number of digits (up to the default of 8).
|
||||
*
|
||||
* You might be tempted to use the built-in n.toString(16) instead, but it doesn't zero-pad and it
|
||||
* doesn't properly convert negative values; for example, if n is -2147483647, then n.toString(16)
|
||||
* will return "-7fffffff" instead of "80000001". Moreover, if n is undefined, n.toString() will throw
|
||||
* an exception, whereas toHex() will simply return '?' characters.
|
||||
*
|
||||
* NOTE: The following work-around (adapted from code found on StackOverflow) would be another solution,
|
||||
* taking care of negative values, zero-padding, and upper-casing, but not undefined/NaN values:
|
||||
*
|
||||
* s = (n < 0? (0xffffffff + n + 1) : n).toString(16);
|
||||
* s = "00000000".substr(0, 8 - s.length) + s;
|
||||
* s = s.substr(0, cch).toUpperCase();
|
||||
*
|
||||
* @param {number|undefined} n is a 32-bit value
|
||||
* @param {number} [cch] is the desired number of hex digits (8 is both the default and the maximum)
|
||||
* @return {string} the hex representation of n
|
||||
*/
|
||||
str.toHex = function(n, cch)
|
||||
{
|
||||
var s = "";
|
||||
if (cch === undefined) {
|
||||
cch = 8;
|
||||
} else {
|
||||
if (cch > 8) cch = 8;
|
||||
}
|
||||
if (isNaN(n)) { // detects BOTH NaN and undefined
|
||||
while (cch-- > 0) s = '?' + s;
|
||||
} else {
|
||||
while (cch-- > 0) {
|
||||
var d = n & 0xf;
|
||||
d += (d >= 0 && d <= 9? 0x30 : 0x41 - 10);
|
||||
s = String.fromCharCode(d) + s;
|
||||
n >>= 4;
|
||||
}
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
/**
|
||||
* toHexByte(b)
|
||||
*
|
||||
* Alias for toHex(b, 2)
|
||||
*
|
||||
* @param {number|undefined} b is a byte value
|
||||
* @return {string} the hex representation of b
|
||||
*/
|
||||
str.toHexByte = function(b)
|
||||
{
|
||||
return str.toHex(b, 2);
|
||||
};
|
||||
|
||||
/**
|
||||
* toHexWord(w)
|
||||
*
|
||||
* Alias for toHex(w, 4)
|
||||
*
|
||||
* @param {number|undefined} w is a word (16-bit) value
|
||||
* @return {string} the hex representation of w
|
||||
*/
|
||||
str.toHexWord = function(w)
|
||||
{
|
||||
return str.toHex(w, 4);
|
||||
};
|
||||
|
||||
/**
|
||||
* toHexAddr(off, sel)
|
||||
*
|
||||
* @param {number} off
|
||||
* @param {number} [sel]
|
||||
* @return {string} the hex representation of sel:off
|
||||
*/
|
||||
str.toHexAddr = function(off, sel)
|
||||
{
|
||||
if (sel !== undefined) {
|
||||
return str.toHexWord(sel) + ":" + str.toHexWord(off);
|
||||
}
|
||||
return str.toHex(off);
|
||||
};
|
||||
|
||||
/**
|
||||
* getBaseName(sFileName, fStripExt)
|
||||
*
|
||||
* This is a poor-man's version of Node's path.basename(), which Node-only components should use instead.
|
||||
*
|
||||
* Note that fStripExt can be used to strip ANY extension, whereas path.basename() will strip the extension only
|
||||
* if it matches the second parameter (eg, path.basename("/foo/bar/baz/asdf/quux.html", ".html") returns "quux").
|
||||
*
|
||||
* @param {string} sFileName
|
||||
* @param {boolean} [fStripExt]
|
||||
* @return {string}
|
||||
*/
|
||||
str.getBaseName = function(sFileName, fStripExt)
|
||||
{
|
||||
var sBaseName = sFileName;
|
||||
var i = sFileName.lastIndexOf("/");
|
||||
if (i >= 0) {
|
||||
sBaseName = sFileName.substr(i + 1);
|
||||
}
|
||||
if (fStripExt) {
|
||||
i = sBaseName.lastIndexOf(".");
|
||||
if (i > 0) {
|
||||
sBaseName = sBaseName.substring(0, i);
|
||||
}
|
||||
}
|
||||
return sBaseName;
|
||||
};
|
||||
|
||||
/**
|
||||
* getExtension(sFileName)
|
||||
*
|
||||
* This is a poor-man's version of Node's path.extname(), which Node-only components should use instead.
|
||||
*
|
||||
* Note that we EXCLUDE the period from the returned extension, whereas path.extname() includes it.
|
||||
*
|
||||
* @param {string} sFileName
|
||||
* @return {string} the filename's extension (in lower-case and EXCLUDING the "."), or an empty string
|
||||
*/
|
||||
str.getExtension = function(sFileName)
|
||||
{
|
||||
var sExtension = "";
|
||||
var i = sFileName.lastIndexOf(".");
|
||||
if (i >= 0) {
|
||||
sExtension = sFileName.substr(i + 1).toLowerCase();
|
||||
}
|
||||
return sExtension;
|
||||
};
|
||||
|
||||
/**
|
||||
* endsWith(s, sSuffix)
|
||||
*
|
||||
* @param {string} s
|
||||
* @param {string} sSuffix
|
||||
* @return {boolean} true if s ends with sSuffix, false if not
|
||||
*/
|
||||
str.endsWith = function(s, sSuffix)
|
||||
{
|
||||
return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1;
|
||||
};
|
||||
|
||||
str.aHTMLEscapeMap = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
|
||||
/**
|
||||
* escapeHTML(sHTML)
|
||||
*
|
||||
* @param {string} sHTML
|
||||
* @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars()
|
||||
*/
|
||||
str.escapeHTML = function(sHTML)
|
||||
{
|
||||
return sHTML.replace(/[&<>"']/g, function(m) {
|
||||
return str.aHTMLEscapeMap[m];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* replaceAll(sFind, sReplace, s)
|
||||
*
|
||||
* @param {string} sFind
|
||||
* @param {string} sReplace
|
||||
* @param {string} s
|
||||
* @return {string}
|
||||
*/
|
||||
str.replaceAll = function(sFind, sReplace, s)
|
||||
{
|
||||
var a = {};
|
||||
a[sFind] = sReplace;
|
||||
return str.replaceArray(a, s);
|
||||
};
|
||||
|
||||
/**
|
||||
* replaceArray(a, s)
|
||||
*
|
||||
* @param {Object} a
|
||||
* @param {string} s
|
||||
* @return {string}
|
||||
*/
|
||||
str.replaceArray = function(a, s)
|
||||
{
|
||||
var sMatch = "";
|
||||
for (var k in a) {
|
||||
k = k.replace(/([\\\[\]\*\{}\(\)\.\+\?])/g, "\\$1");
|
||||
sMatch += (sMatch? '|' : '') + k;
|
||||
}
|
||||
return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m) {
|
||||
return a[m];
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* pad(s, cch)
|
||||
*
|
||||
* Note that the maximum amount of padding currently supported is 40 spaces.
|
||||
*
|
||||
* @param {string} s is a string
|
||||
* @param {number} cch is desired length
|
||||
* @returns {string} the original string (s) with spaces padding it to the specified length
|
||||
*/
|
||||
str.pad = function(s, cch)
|
||||
{
|
||||
return s + " ".substr(0, cch - s.length);
|
||||
};
|
||||
|
||||
/**
|
||||
* trim(s)
|
||||
*
|
||||
* @param {string} s
|
||||
* @returns {string}
|
||||
*/
|
||||
str.trim = function(s)
|
||||
{
|
||||
if (String.prototype.trim) {
|
||||
return s.trim();
|
||||
}
|
||||
return s.replace(/^\s+|\s+$/g, "");
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = str;
|
||||
73
my_modules/shared/lib/userapi.js
Normal file
73
my_modules/shared/lib/userapi.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* @fileoverview User API, as defined by httpapi.js
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a>
|
||||
* @version 1.0
|
||||
* Created 2014-May-13
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
* Examples of User API requests:
|
||||
*
|
||||
* web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser;
|
||||
*/
|
||||
|
||||
var UserAPI = {
|
||||
ENDPOINT: "/api/v1/user",
|
||||
QUERY: {
|
||||
REQ: "req", // specifies a request
|
||||
USER: "user", // specifies a user ID
|
||||
STATE: "state", // specifies a state ID
|
||||
DATA: "data" // specifies state data
|
||||
},
|
||||
REQ: {
|
||||
CREATE: "create", // creates a user ID
|
||||
VERIFY: "verify", // requests verification of a user ID
|
||||
STORE: "store", // stores a machine state on the server
|
||||
LOAD: "load" // loads a machine state from the server
|
||||
},
|
||||
RES: {
|
||||
CODE: "code",
|
||||
DATA: "data"
|
||||
},
|
||||
CODE: {
|
||||
OK: "ok",
|
||||
FAIL: "error"
|
||||
},
|
||||
FAIL: {
|
||||
DUPLICATE: "user already exists",
|
||||
VERIFY: "unable to verify user",
|
||||
BADSTATE: "invalid state parameter",
|
||||
NOSTATE: "no machine state",
|
||||
BADLOAD: "unable to load machine state",
|
||||
BADSTORE: "unable to save machine state"
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = UserAPI;
|
||||
212
my_modules/shared/lib/usrlib.js
Normal file
212
my_modules/shared/lib/usrlib.js
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/**
|
||||
* @fileoverview Assorted helper functions
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
|
||||
* @version 1.0
|
||||
* Created 2014-03-09
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var usr = {};
|
||||
|
||||
/**
|
||||
* binarySearch(a, v, fnCompare)
|
||||
*
|
||||
* @param {Array} a is an array
|
||||
* @param {number|string|Array|Object} v
|
||||
* @param {function((number|string), (number|string))} [fnCompare]
|
||||
* @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point
|
||||
*/
|
||||
usr.binarySearch = function(a, v, fnCompare) {
|
||||
var left = 0;
|
||||
var right = a.length;
|
||||
var found = 0;
|
||||
if (fnCompare === undefined) {
|
||||
fnCompare = function(a, b) {
|
||||
return a > b? 1 : a < b? -1 : 0;
|
||||
};
|
||||
}
|
||||
while (left < right) {
|
||||
var middle = (left + right) >> 1;
|
||||
var compareResult;
|
||||
compareResult = fnCompare(v, a[middle]);
|
||||
if (compareResult > 0) {
|
||||
left = middle + 1;
|
||||
} else {
|
||||
right = middle;
|
||||
found = !compareResult;
|
||||
}
|
||||
}
|
||||
return found? left : ~left;
|
||||
};
|
||||
|
||||
/**
|
||||
* binaryInsert(a, v, fnCompare)
|
||||
*
|
||||
* If element v already exists in array a, the array is unchanged (we don't allow duplicates); otherwise, the
|
||||
* element is inserted into the array at the appropriate index.
|
||||
*
|
||||
* @param {Array} a is an array
|
||||
* @param {number|string|Array|Object} v is the value to insert
|
||||
* @param {function((number|string), (number|string))} [fnCompare]
|
||||
*/
|
||||
usr.binaryInsert = function(a, v, fnCompare) {
|
||||
var index = usr.binarySearch(a, v, fnCompare);
|
||||
if (index < 0) {
|
||||
a.splice(-(index + 1), 0, v);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* getTime()
|
||||
*
|
||||
* @return {number} the current time, in milliseconds
|
||||
*/
|
||||
usr.getTime = function() {
|
||||
return Date.now() || +new Date();
|
||||
};
|
||||
|
||||
/**
|
||||
* getTimestamp()
|
||||
*
|
||||
* @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss")
|
||||
*/
|
||||
usr.getTimestamp = function() {
|
||||
var date = new Date();
|
||||
var padNum = function(n) {
|
||||
return (n < 10? "0" : "") + n;
|
||||
};
|
||||
return date.getFullYear() + "-" + padNum(date.getMonth() + 1) + "-" + padNum(date.getDate()) + " " + padNum(date.getHours()) + ":" + padNum(date.getMinutes()) + ":" + padNum(date.getSeconds());
|
||||
};
|
||||
|
||||
/**
|
||||
* getMonthDays(nMonth, nYear)
|
||||
*
|
||||
* Note that if we're being called on behalf of the RTC, its year is always truncated to two digits (mod 100),
|
||||
* so we have no idea what century the year 0 might refer to. When using the normal leap-year formula, 0 fails
|
||||
* the mod 100 test but passes the mod 400 test, so as far as the RTC is concerned, every century year is a leap
|
||||
* year. Since we're most likely dealing with the year 2000, that's fine, since 2000 was also a leap year.
|
||||
*
|
||||
* TODO: There IS a separate CMOS byte that's supposed to be set to CMOS_ADDR.CENTURY_DATE; it's always BCD,
|
||||
* so theoretically it will contain values like 0x19 or 0x20 (for the 20th and 21st centuries, respectively), and
|
||||
* we could add that as another parameter to this function, to improve the accuracy, but that would go beyond what
|
||||
* a real RTC actually does.
|
||||
*
|
||||
* @param {number} nMonth (1-12)
|
||||
* @param {number} nYear (normally a 4-digit year, but it may also be mod 100)
|
||||
* @return {number} the maximum (1-based) day allowed for the specified month and year
|
||||
*/
|
||||
usr.getMonthDays = function(nMonth, nYear)
|
||||
{
|
||||
var nDays = usr.aMonthDays[nMonth - 1];
|
||||
if (nDays == 28) {
|
||||
if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) {
|
||||
nDays++;
|
||||
}
|
||||
}
|
||||
return nDays;
|
||||
};
|
||||
|
||||
usr.asDays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
|
||||
usr.asMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
|
||||
usr.aMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
||||
|
||||
/**
|
||||
* formatDate(sFormat, date)
|
||||
*
|
||||
* @param {string} sFormat (eg, "F j, Y", "Y-m-d H:i:s")
|
||||
* @param {Date} [date] (default is the current time)
|
||||
* @return {string}
|
||||
*
|
||||
* Supported identifiers in sFormat include:
|
||||
*
|
||||
* a: lowercase ante meridiem and post meridiem (am or pm)
|
||||
* d: day of the month, 2 digits with leading zeros (01,...,31)
|
||||
* g: hour in 12-hour format, without leading zeros (1,...,12)
|
||||
* i: minutes, with leading zeros (00,...,59)
|
||||
* j: day of the month, without leading zeros (1,...,31)
|
||||
* l: day of the week ("Sunday",...,"Saturday")
|
||||
* m: month, with leading zeros (01,...,12)
|
||||
* s: seconds, with leading zeros (00,...,59)
|
||||
* F: month ("January",...,"December")
|
||||
* H: hour in 24-hour format, with leading zeros (00,...,23)
|
||||
* Y: year (eg, 2014)
|
||||
*
|
||||
* For more inspiration, see: http://php.net/manual/en/function.date.php
|
||||
*/
|
||||
usr.formatDate = function(sFormat, date) {
|
||||
var sDate = "";
|
||||
if (!date) date = new Date();
|
||||
var iHour = date.getHours();
|
||||
var iDay = date.getDate();
|
||||
var iMonth = date.getMonth() + 1;
|
||||
for (var i = 0; i < sFormat.length; i++) {
|
||||
var ch;
|
||||
switch((ch = sFormat.charAt(i))) {
|
||||
case 'a':
|
||||
sDate += (iHour < 12? "am" : "pm");
|
||||
break;
|
||||
case 'd':
|
||||
sDate += ('0' + iDay).slice(-2);
|
||||
break;
|
||||
case 'g':
|
||||
sDate += (!iHour? 12 : (iHour > 12? iHour - 12 : iHour));
|
||||
break;
|
||||
case 'i':
|
||||
sDate += ('0' + date.getMinutes()).slice(-2);
|
||||
break;
|
||||
case 'j':
|
||||
sDate += iDay;
|
||||
break;
|
||||
case 'l':
|
||||
sDate += usr.asDays[date.getDay()];
|
||||
break;
|
||||
case 'm':
|
||||
sDate += ('0' + iMonth).slice(-2);
|
||||
break;
|
||||
case 's':
|
||||
sDate += ('0' + date.getSeconds()).slice(-2);
|
||||
break;
|
||||
case 'F':
|
||||
sDate += usr.asMonths[iMonth - 1];
|
||||
break;
|
||||
case 'H':
|
||||
sDate += ('0' + iHour).slice(-2);
|
||||
break;
|
||||
case 'Y':
|
||||
sDate += date.getFullYear();
|
||||
break;
|
||||
default:
|
||||
sDate += ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sDate;
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = usr;
|
||||
647
my_modules/shared/lib/weblib.js
Normal file
647
my_modules/shared/lib/weblib.js
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
/**
|
||||
* @fileoverview Browser-related helper functions
|
||||
* @author <a href="mailto:Jeff@pcjs.org">Jeff Parsons</a> (@jeffpar)
|
||||
* @version 1.0
|
||||
* Created 2014-05-08
|
||||
*
|
||||
* Copyright © 2012-2014 Jeff Parsons <Jeff@pcjs.org>
|
||||
*
|
||||
* This file is part of the JavaScript Machines Project (aka JSMachines) at <http://jsmachines.net/>
|
||||
* and <http://pcjs.org/>.
|
||||
*
|
||||
* JSMachines is free software: you can redistribute it and/or modify it under the terms of the
|
||||
* GNU General Public License as published by the Free Software Foundation, either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* JSMachines is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
||||
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with JSMachines.
|
||||
* If not, see <http://www.gnu.org/licenses/gpl.html>.
|
||||
*
|
||||
* You are required to include the above copyright notice in every source code file of every
|
||||
* copy or modified version of this work, and to display that copyright notice on every screen
|
||||
* that loads or runs any version of this software (see Computer.sCopyright).
|
||||
*
|
||||
* Some JSMachines files also attempt to load external resource files, such as character-image files,
|
||||
* ROM files, and disk image files. Those external resource files are not considered part of the
|
||||
* JSMachines Project for purposes of the GNU General Public License, and the author does not claim
|
||||
* any copyright as to their contents.
|
||||
*/
|
||||
|
||||
/*
|
||||
* According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties
|
||||
* and functions of JavaScript-in-the-Browser:
|
||||
*
|
||||
* Property Description
|
||||
* ---
|
||||
* Infinity A numeric value that represents positive/negative infinity
|
||||
* NaN "Not-a-Number" value
|
||||
* undefined Indicates that a variable has not been assigned a value
|
||||
*
|
||||
* Function Description
|
||||
* ---
|
||||
* decodeURI() Decodes a URI
|
||||
* decodeURIComponent() Decodes a URI component
|
||||
* encodeURI() Encodes a URI
|
||||
* encodeURIComponent() Encodes a URI component
|
||||
* escape() Deprecated in version 1.5. Use encodeURI() or encodeURIComponent() instead
|
||||
* eval() Evaluates a string and executes it as if it was script code
|
||||
* isFinite() Determines whether a value is a finite, legal number
|
||||
* isNaN() Determines whether a value is an illegal number
|
||||
* Number() Converts an object's value to a number
|
||||
* parseFloat() Parses a string and returns a floating point number
|
||||
* parseInt() Parses a string and returns an integer
|
||||
* String() Converts an object's value to a string
|
||||
* unescape() Deprecated in version 1.5. Use decodeURI() or decodeURIComponent() instead
|
||||
*
|
||||
* And according to http://www.w3schools.com/jsref/obj_window.asp, these are the properties and functions
|
||||
* of the *window* object.
|
||||
*
|
||||
* Property Description
|
||||
* ---
|
||||
* closed Returns a Boolean value indicating whether a window has been closed or not
|
||||
* defaultStatus Sets or returns the default text in the statusbar of a window
|
||||
* document Returns the Document object for the window (See Document object)
|
||||
* frames Returns an array of all the frames (including iframes) in the current window
|
||||
* history Returns the History object for the window (See History object)
|
||||
* innerHeight Returns the inner height of a window's content area
|
||||
* innerWidth Returns the inner width of a window's content area
|
||||
* length Returns the number of frames (including iframes) in a window
|
||||
* location Returns the Location object for the window (See Location object)
|
||||
* name Sets or returns the name of a window
|
||||
* navigator Returns the Navigator object for the window (See Navigator object)
|
||||
* opener Returns a reference to the window that created the window
|
||||
* outerHeight Returns the outer height of a window, including toolbars/scrollbars
|
||||
* outerWidth Returns the outer width of a window, including toolbars/scrollbars
|
||||
* pageXOffset Returns the pixels the current document has been scrolled (horizontally) from the upper left corner of the window
|
||||
* pageYOffset Returns the pixels the current document has been scrolled (vertically) from the upper left corner of the window
|
||||
* parent Returns the parent window of the current window
|
||||
* screen Returns the Screen object for the window (See Screen object)
|
||||
* screenLeft Returns the x coordinate of the window relative to the screen
|
||||
* screenTop Returns the y coordinate of the window relative to the screen
|
||||
* screenX Returns the x coordinate of the window relative to the screen
|
||||
* screenY Returns the y coordinate of the window relative to the screen
|
||||
* self Returns the current window
|
||||
* status Sets or returns the text in the statusbar of a window
|
||||
* top Returns the topmost browser window
|
||||
*
|
||||
* Method Description
|
||||
* ---
|
||||
* alert() Displays an alert box with a message and an OK button
|
||||
* atob() Decodes a base-64 encoded string
|
||||
* blur() Removes focus from the current window
|
||||
* btoa() Encodes a string in base-64
|
||||
* clearInterval() Clears a timer set with setInterval()
|
||||
* clearTimeout() Clears a timer set with setTimeout()
|
||||
* close() Closes the current window
|
||||
* confirm() Displays a dialog box with a message and an OK and a Cancel button
|
||||
* createPopup() Creates a pop-up window
|
||||
* focus() Sets focus to the current window
|
||||
* moveBy() Moves a window relative to its current position
|
||||
* moveTo() Moves a window to the specified position
|
||||
* open() Opens a new browser window
|
||||
* print() Prints the content of the current window
|
||||
* prompt() Displays a dialog box that prompts the visitor for input
|
||||
* resizeBy() Resizes the window by the specified pixels
|
||||
* resizeTo() Resizes the window to the specified width and height
|
||||
* scroll() This method has been replaced by the scrollTo() method.
|
||||
* scrollBy() Scrolls the content by the specified number of pixels
|
||||
* scrollTo() Scrolls the content to the specified coordinates
|
||||
* setInterval() Calls a function or evaluates an expression at specified intervals (in milliseconds)
|
||||
* setTimeout() Calls a function or evaluates an expression after a specified number of milliseconds
|
||||
* stop() Stops the window from loading
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/* global window: true, setTimeout: false, clearTimeout: false, SITEHOST: false */
|
||||
|
||||
/*
|
||||
* We must defer loading the Component module until the function(s) requiring it are
|
||||
* called; otherwise, we create an initialization cycle in which Component requires weblib
|
||||
* and weblib requires Component.
|
||||
*
|
||||
* In an ideal world, weblib would not be dependent on Component, but we really want to use
|
||||
* its logging functions.
|
||||
*/
|
||||
if (typeof module !== 'undefined') {
|
||||
var Component;
|
||||
require("./defines");
|
||||
var str = require("./strlib");
|
||||
var ReportAPI = require("./reportapi");
|
||||
}
|
||||
|
||||
var web = {};
|
||||
|
||||
/**
|
||||
* loadResource(sURL, fAsync, data, componentNotify, fnNotify, pNotify)
|
||||
*
|
||||
* Request the specified resource (sURL), and once the request is complete,
|
||||
* optionally call the specified method (fnNotify) of the specified component (componentNotify).
|
||||
*
|
||||
* TODO: Figure out how we can strongly type the fnNotify parameter, because the Closure Compiler has issues with:
|
||||
*
|
||||
* {function(this:Component, string, (string|null), number, (number|string|null|Object|Array|undefined))} [fnNotify]
|
||||
*
|
||||
* @param {string} sURL
|
||||
* @param {boolean} [fAsync] is true for an asynchronous request
|
||||
* @param {Object|null} [data] for a POST request (default is a GET request)
|
||||
* @param {Component} [componentNotify]
|
||||
* @param {function(...)} [fnNotify]
|
||||
* @param {number|string|null|Object|Array} [pNotify] optional fnNotify info parameter
|
||||
* @return {Array} containing errorCode and responseText (empty array if fAsync is true)
|
||||
*/
|
||||
web.loadResource = function(sURL, fAsync, data, componentNotify, fnNotify, pNotify)
|
||||
{
|
||||
fAsync = !!fAsync; // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it)
|
||||
if (typeof module !== 'undefined') {
|
||||
/*
|
||||
* We don't even need to load Component, because we can't use any of the code below
|
||||
* within Node anyway. Instead, we must hand this request off to our network library.
|
||||
*
|
||||
* if (!Component) Component = require("./component");
|
||||
*/
|
||||
var net = require("./netlib");
|
||||
return net.loadResource(sURL, fAsync, data, componentNotify, fnNotify, pNotify);
|
||||
}
|
||||
var nErrorCode = 0;
|
||||
var sURLData = null;
|
||||
var sURLName = str.getBaseName(sURL);
|
||||
var xmlHTTP = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP"));
|
||||
if (fAsync) {
|
||||
xmlHTTP.onreadystatechange = function() {
|
||||
if (xmlHTTP.readyState === 4) {
|
||||
/*
|
||||
* The following line is recommended for WebKit, as a work-around to prevent the handler firing multiple
|
||||
* times when debugging. Unfortunately, that's not the only XMLHttpRequest problem that occurs when
|
||||
* debugging, so I think the WebKit problem is deeper than that. When we have multiple XMLHttpRequests
|
||||
* pending, any debugging activity means most of them simply get dropped on floor, so what may actually be
|
||||
* happening are mis-notifications rather than redundant notifications.
|
||||
*/
|
||||
xmlHTTP.onreadystatechange = undefined;
|
||||
sURLData = xmlHTTP.responseText;
|
||||
if (xmlHTTP.status == 200) {
|
||||
Component.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sURLData.length + " bytes");
|
||||
}
|
||||
else {
|
||||
nErrorCode = xmlHTTP.status || -1;
|
||||
Component.log("xmlHTTP.onreadystatechange(" + sURL + "): error code " + nErrorCode);
|
||||
}
|
||||
if (componentNotify && fnNotify) fnNotify.call(componentNotify, sURLName, sURLData, nErrorCode, pNotify);
|
||||
}
|
||||
};
|
||||
}
|
||||
if (data) {
|
||||
var sData = "";
|
||||
for (var p in data) {
|
||||
if (!data.hasOwnProperty(p)) continue;
|
||||
if (sData) sData += "&";
|
||||
sData += p + '=' + encodeURIComponent(data[p]);
|
||||
}
|
||||
sData = sData.replace(/%20/g, '+');
|
||||
Component.log("web.loadResource(POST " + sURL + "): " + sData.length + " bytes");
|
||||
xmlHTTP.open("POST", sURL, fAsync);
|
||||
xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
|
||||
xmlHTTP.send(sData);
|
||||
} else {
|
||||
Component.log("web.loadResource(GET " + sURL + ")");
|
||||
xmlHTTP.open("GET", sURL, fAsync);
|
||||
xmlHTTP.send();
|
||||
}
|
||||
var response = [];
|
||||
if (!fAsync) {
|
||||
sURLData = xmlHTTP.responseText;
|
||||
if (xmlHTTP.status == 200) {
|
||||
Component.log("web.loadResource(" + sURL + "): returned " + sURLData.length + " bytes");
|
||||
} else {
|
||||
nErrorCode = xmlHTTP.status || -1;
|
||||
Component.log("web.loadResource(" + sURL + "): error code " + nErrorCode);
|
||||
}
|
||||
if (componentNotify && fnNotify) fnNotify.call(componentNotify, sURLName, sURLData, nErrorCode, pNotify);
|
||||
response = [nErrorCode, sURLData];
|
||||
}
|
||||
return response;
|
||||
};
|
||||
|
||||
/**
|
||||
* sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
|
||||
*
|
||||
* Send a report (eg, bug report) to the server.
|
||||
*
|
||||
* @param {string} sApp (eg, "PCjs")
|
||||
* @param {string} sVer (eg, "1.02")
|
||||
* @param {string} sURL (eg, "/configs/pc/machines/5150/mda/64kb/index.xml")
|
||||
* @param {string} sUser (ie, the user key, if any)
|
||||
* @param {string} sType (eg, "bug"); one of ReportAPI.TYPE.*
|
||||
* @param {string} sReport (eg, unparsed state data)
|
||||
* @param {string} [sHostName] (default is http://SITEHOST)
|
||||
*/
|
||||
web.sendReport = function(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
|
||||
{
|
||||
var data = {};
|
||||
data[ReportAPI.QUERY.APP] = sApp;
|
||||
data[ReportAPI.QUERY.VER] = sVer;
|
||||
data[ReportAPI.QUERY.URL] = sURL;
|
||||
data[ReportAPI.QUERY.USER] = sUser;
|
||||
data[ReportAPI.QUERY.TYPE] = sType;
|
||||
data[ReportAPI.QUERY.DATA] = sReport;
|
||||
var sReportURL = (sHostName? sHostName : "http://" + SITEHOST) + ReportAPI.ENDPOINT;
|
||||
web.loadResource(sReportURL, true, data);
|
||||
};
|
||||
|
||||
/**
|
||||
* getHost()
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
web.getHost = function()
|
||||
{
|
||||
return ("http://" + (window? window.location.host : SITEHOST));
|
||||
};
|
||||
|
||||
/**
|
||||
* getHostURL()
|
||||
*
|
||||
* @return {string|null}
|
||||
*/
|
||||
web.getHostURL = function()
|
||||
{
|
||||
return (window? window.location.href : null);
|
||||
};
|
||||
|
||||
/**
|
||||
* getUserAgent()
|
||||
*
|
||||
* @return {string}
|
||||
*/
|
||||
web.getUserAgent = function()
|
||||
{
|
||||
return (window? window.navigator.userAgent : "");
|
||||
};
|
||||
|
||||
/**
|
||||
* alertUser(sMessage)
|
||||
*
|
||||
* @param {string} sMessage
|
||||
*/
|
||||
web.alertUser = function(sMessage)
|
||||
{
|
||||
if (window) {
|
||||
window.alert(sMessage);
|
||||
} else {
|
||||
console.log(sMessage);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* confirmUser(sPrompt)
|
||||
*
|
||||
* @param {string} sPrompt
|
||||
* @returns {boolean} true if the user clicked OK, false if Cancel/Close
|
||||
*/
|
||||
web.confirmUser = function(sPrompt)
|
||||
{
|
||||
var fResponse = false;
|
||||
if (window) {
|
||||
fResponse = window.confirm(sPrompt);
|
||||
}
|
||||
return fResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* promptUser()
|
||||
*
|
||||
* @param {string} sPrompt
|
||||
* @param {string} [sDefault]
|
||||
* @returns {string|null}
|
||||
*/
|
||||
web.promptUser = function(sPrompt, sDefault)
|
||||
{
|
||||
var sResponse = null;
|
||||
if (window) {
|
||||
sResponse = window.prompt(sPrompt, sDefault === undefined? "" : sDefault);
|
||||
}
|
||||
return sResponse;
|
||||
};
|
||||
|
||||
/**
|
||||
* getLocalStorageItem(sKey)
|
||||
*
|
||||
* Returns the requested key value, or null if the key does not exist, or undefined if localStorage is not available
|
||||
*
|
||||
* @param {string} sKey
|
||||
* @return {string|null|undefined} sValue
|
||||
*/
|
||||
web.getLocalStorageItem = function(sKey)
|
||||
{
|
||||
var sValue;
|
||||
if (window) {
|
||||
sValue = window.localStorage.getItem(sKey);
|
||||
}
|
||||
return sValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* setLocalStorageItem(sKey, sValue)
|
||||
*
|
||||
* @param {string} sKey
|
||||
* @param {string} sValue
|
||||
* return {boolean} true if localStorage is available, false if not
|
||||
*/
|
||||
web.setLocalStorageItem = function(sKey, sValue)
|
||||
{
|
||||
if (window) {
|
||||
window.localStorage.setItem(sKey, sValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* reloadPage()
|
||||
*/
|
||||
web.reloadPage = function()
|
||||
{
|
||||
if (window) window.location.reload();
|
||||
};
|
||||
|
||||
/**
|
||||
* isUserAgent(s)
|
||||
*
|
||||
* Check the browser's user-agent string for the given substring; "iOS" and "MSIE" are special values you can
|
||||
* use that will match any iOS or MSIE browser, respectively (even IE11, in the case of "MSIE").
|
||||
*
|
||||
* 2013-11-06: In a questionable move, MSFT changed the user-agent reported by IE11 on Windows 8.1, eliminating
|
||||
* the "MSIE" string (which MSDN calls a "version token"; see http://msdn.microsoft.com/library/ms537503.aspx);
|
||||
* they say "public websites should rely on feature detection, rather than browser detection, in order to design
|
||||
* their sites for browsers that don't support the features used by the website." So, in IE11, we get a user-agent
|
||||
* that tries to fool apps into thinking the browser is more like WebKit or Gecko:
|
||||
*
|
||||
* Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko
|
||||
*
|
||||
* That's a nice idea, but in the meantime, they hosed the XSL transform code in embed.js, which contained
|
||||
* some very critical browser-specific code; turning on IE's "Compatibility Mode" didn't help either, because
|
||||
* that's a sledgehammer solution which restores the old user-agent string but also disables other features like
|
||||
* HTML5 canvas support. As an interim solution, I'm treating any "MSIE" check as a check for either "MSIE" or
|
||||
* "Trident".
|
||||
*
|
||||
* UPDATE: I've since found ways to make the code in embed.js more browser-agnostic, so for now, there's isn't
|
||||
* any code that cares about "MSIE", but I've left the change in place, because I wouldn't be surprised if I'll
|
||||
* need more IE-specific code in the future, perhaps for things like copy/paste functionality, or mouse capture.
|
||||
*
|
||||
* @param {string} s is a substring to search for in the user-agent; as noted above, "iOS" and "MSIE" are special values
|
||||
* @return {boolean} is true if the string was found, false if not
|
||||
*/
|
||||
web.isUserAgent = function(s)
|
||||
{
|
||||
if (window) {
|
||||
var userAgent = web.getUserAgent();
|
||||
/*
|
||||
* Here's one case where we have to be careful with Component, because when isUserAgent() is called by
|
||||
* the init code below, component.js hasn't been loaded yet. The simplest solution is to remove the call.
|
||||
*
|
||||
* if (Component) Component.log("agent: " + userAgent);
|
||||
*
|
||||
* And yes, it would be pointless to use the conditional (?) operator below, if not for the Google Closure
|
||||
* Compiler (v20130823) failing to detect the entire expression as a boolean.
|
||||
*/
|
||||
return (s == "iOS" && userAgent.match(/(iPod|iPhone|iPad)/) && userAgent.match(/AppleWebKit/) || s == "MSIE" && userAgent.match(/(MSIE|Trident)/) || (userAgent.indexOf(s) >= 0))? true : false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* getURLParameters(sParms)
|
||||
*
|
||||
* @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?')
|
||||
* @return {Object} containing properties for each parameter found
|
||||
*/
|
||||
web.getURLParameters = function(sParms)
|
||||
{
|
||||
var aParms = {};
|
||||
if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") has been invoked
|
||||
if (!sParms) {
|
||||
/*
|
||||
* Note that window.location.href returns the entire URL, whereas window.location.search returns
|
||||
* only the parameters, if any (starting with the '?', which we skip over with a substr() call).
|
||||
*/
|
||||
sParms = window.location.search.substr(1);
|
||||
}
|
||||
var match;
|
||||
var pl = /\+/g; // RegExp for replacing addition symbol with a space
|
||||
var search = /([^&=]+)=?([^&]*)/g;
|
||||
var decode = function(s) { return decodeURIComponent(s.replace(pl, " ")); };
|
||||
|
||||
while ((match = search.exec(sParms))) {
|
||||
aParms[decode(match[1])] = decode(match[2]);
|
||||
}
|
||||
}
|
||||
return aParms;
|
||||
};
|
||||
|
||||
/**
|
||||
* onCountRepeat(n, fn, fnComplete, msDelay)
|
||||
*
|
||||
* Call fn() n times with an msDelay millisecond delay between calls, then
|
||||
* call fnComplete() when the count has been exhausted OR fn() returns false.
|
||||
*
|
||||
* @param {number} n
|
||||
* @param {function()} fn
|
||||
* @param {function()} fnComplete
|
||||
* @param {number} [msDelay]
|
||||
*/
|
||||
web.onCountRepeat = function(n, fn, fnComplete, msDelay)
|
||||
{
|
||||
var fnRepeat = function doCountRepeat() {
|
||||
n -= 1;
|
||||
if (n >= 0) {
|
||||
if (!fn()) n = 0;
|
||||
}
|
||||
if (n > 0) {
|
||||
setTimeout(fnRepeat, msDelay || 0);
|
||||
return;
|
||||
}
|
||||
fnComplete();
|
||||
};
|
||||
fnRepeat();
|
||||
};
|
||||
|
||||
/**
|
||||
* onClickRepeat(e, msDelay, msRepeat, fn)
|
||||
*
|
||||
* Repeatedly call fn() with an initial msDelay, and an msRepeat delay thereafter,
|
||||
* as long as HTML control Object e has an active "down" event and fn() returns true.
|
||||
*
|
||||
* @param {Object} e
|
||||
* @param {number} msDelay
|
||||
* @param {number} msRepeat
|
||||
* @param {function(boolean)} fn is passed false on the first call, true on all repeated calls
|
||||
*/
|
||||
web.onClickRepeat = function(e, msDelay, msRepeat, fn)
|
||||
{
|
||||
var ms = 0, timer = null, fIgnoreMouseEvents = false;
|
||||
|
||||
var fnRepeat = function doClickRepeat() {
|
||||
if (fn(ms === msRepeat)) {
|
||||
timer = setTimeout(fnRepeat, ms);
|
||||
ms = msRepeat;
|
||||
}
|
||||
};
|
||||
e.onmousedown = function() {
|
||||
// Component.println("onMouseDown()");
|
||||
if (!fIgnoreMouseEvents) {
|
||||
if (!timer) {
|
||||
ms = msDelay;
|
||||
fnRepeat();
|
||||
}
|
||||
}
|
||||
};
|
||||
e.ontouchstart = function() {
|
||||
// Component.println("onTouchStart()");
|
||||
if (!timer) {
|
||||
ms = msDelay;
|
||||
fnRepeat();
|
||||
}
|
||||
};
|
||||
e.onmouseup = e.onmouseout = function() {
|
||||
// Component.println("onMouseUp()/onMouseOut()");
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
};
|
||||
e.ontouchend = e.ontouchcancel = function() {
|
||||
// Component.println("onTouchEnd()/onTouchCancel()");
|
||||
if (timer) {
|
||||
clearTimeout(timer);
|
||||
timer = null;
|
||||
}
|
||||
/*
|
||||
* Devices that generate ontouch* events ALSO generate onmouse* events,
|
||||
* and generally do so immediately after all the touch events are complete,
|
||||
* so unless we want double the action, we need to ignore mouse events.
|
||||
*/
|
||||
fIgnoreMouseEvents = true;
|
||||
};
|
||||
};
|
||||
|
||||
web.aPageEventHandlers = {
|
||||
'init': [], // list of window 'onload' handlers
|
||||
'show': [], // list of window 'onpageshow' handlers
|
||||
'exit': [] // list of window 'onunload' handlers (although we prefer to use 'onbeforeunload' if possible)
|
||||
};
|
||||
|
||||
web.fPageEventsEnabled = true;
|
||||
|
||||
/**
|
||||
* onPageEvent(sName, fn)
|
||||
*
|
||||
* @param {string} sFunc
|
||||
* @param {function()} fn
|
||||
*
|
||||
* Use this instead of setting window['onunload'], window['onunload'], etc.
|
||||
* Allows multiple JavaScript modules to define a handler for the same event.
|
||||
*
|
||||
* Moreover, it's risky to refer to obscure event handlers with "dot" names, because
|
||||
* the Closure Compiler may erroneously replace them (eg, window.onpageshow is a good example).
|
||||
*/
|
||||
web.onPageEvent = function(sFunc, fn)
|
||||
{
|
||||
if (window) {
|
||||
var fnPrev = window[sFunc];
|
||||
if (typeof fnPrev !== 'function') {
|
||||
window[sFunc] = fn;
|
||||
} else {
|
||||
/*
|
||||
* TODO: Determine whether there's any value in receiving/sending the Event object that the
|
||||
* browser provides when it generates the original event.
|
||||
*/
|
||||
window[sFunc] = function onWindowEvent() {
|
||||
if (fnPrev) fnPrev();
|
||||
fn();
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* onInit(fn)
|
||||
*
|
||||
* @param {function()} fn
|
||||
*
|
||||
* Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler.
|
||||
*/
|
||||
web.onInit = function(fn)
|
||||
{
|
||||
web.aPageEventHandlers['init'].push(fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* onShow(fn)
|
||||
*
|
||||
* @param {function()} fn
|
||||
*
|
||||
* Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler.
|
||||
*/
|
||||
web.onShow = function(fn)
|
||||
{
|
||||
web.aPageEventHandlers['show'].push(fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* onExit(fn)
|
||||
*
|
||||
* @param {function()} fn
|
||||
*
|
||||
* Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler.
|
||||
*/
|
||||
web.onExit = function(fn)
|
||||
{
|
||||
web.aPageEventHandlers['exit'].push(fn);
|
||||
};
|
||||
|
||||
/**
|
||||
* doPageEvent(afn)
|
||||
*
|
||||
* @param {Array.<function()>} afn
|
||||
*/
|
||||
web.doPageEvent = function(afn)
|
||||
{
|
||||
if (web.fPageEventsEnabled) {
|
||||
for (var i = 0; i < afn.length; i++) {
|
||||
afn[i]();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* enablePageEvents(fEnable)
|
||||
*
|
||||
* @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default)
|
||||
*/
|
||||
web.enablePageEvents = function(fEnable)
|
||||
{
|
||||
web.fPageEventsEnabled = fEnable;
|
||||
};
|
||||
|
||||
/**
|
||||
* sendPageEvent(sEvent)
|
||||
*
|
||||
* This allows us to manually trigger page events.
|
||||
*
|
||||
* @param {string} sEvent (one of 'init', 'show' or 'exit')
|
||||
*/
|
||||
web.sendPageEvent = function(sEvent)
|
||||
{
|
||||
if (web.aPageEventHandlers[sEvent]) {
|
||||
web.doPageEvent(web.aPageEventHandlers[sEvent]);
|
||||
}
|
||||
};
|
||||
|
||||
web.onPageEvent('onload', function onPageLoad() { web.doPageEvent(web.aPageEventHandlers['init']); });
|
||||
web.onPageEvent('onpageshow', function onPageShow() { web.doPageEvent(web.aPageEventHandlers['show']); });
|
||||
web.onPageEvent(web.isUserAgent("Opera") || web.isUserAgent("iOS")? 'onunload' : 'onbeforeunload', function onPageUnload() { web.doPageEvent(web.aPageEventHandlers['exit']); });
|
||||
|
||||
if (typeof module !== 'undefined') module.exports = web;
|
||||
Loading…
Reference in a new issue