entries whose paths do not appear as one of the
@@ -169,10 +167,10 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
}
}
}
- sXMLFile = sName = str.getBaseName(sName);
+ sXMLFile = sName = Str.getBaseName(sName);
}
else if (sExt == "xsl") {
- sXSLFile = sName = str.getBaseName(sName);
+ sXSLFile = sName = Str.getBaseName(sName);
}
Component.log("saving resource: '" + sName + "' (" + data.length + " bytes)");
resNew[sName] = data;
@@ -202,7 +200,7 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
sPCJS = sPCJS.replace(/\u00A9/g, "©");
- var sAlert = web.downloadFile(sPCJS, "javascript", false, sScript);
+ var sAlert = Web.downloadFile(sPCJS, "javascript", false, sScript);
sAlert += ', copy it to your web server as "' + sScript + '", and then add the following to your web page:\n\n';
sAlert += '\n';
@@ -210,10 +208,10 @@ function downloadPC(sURL, sCSS, nErrorCode, aMachineInfo)
sAlert += '\n';
sAlert += '\n\n';
sAlert += 'The machine should appear where the is located.';
- web.alertUser(sAlert);
+ Component.alertUser(sAlert);
return;
}
- web.alertUser("Missing XML/XSL resources");
+ Component.alertUser("Missing XML/XSL resources");
}
/**
diff --git a/modules/shared/lib/state.js b/modules/shared/lib/state.js
index 53a181e09..94844fab0 100644
--- a/modules/shared/lib/state.js
+++ b/modules/shared/lib/state.js
@@ -29,183 +29,46 @@
"use strict";
if (NODE) {
- var web = require("./../../shared/lib/weblib");
- var Component = require("./../../shared/lib/component");
+ var Web = require("../../shared/lib/weblib");
+ var Component = require("../../shared/lib/component");
}
-/**
- * State(component, sVersion, sSuffix)
- *
- * State objects are used by components to save/restore their state.
- *
- * During a save operation, components add data to a State object via set(), and then return
- * the resulting data using data().
- *
- * During a restore operation, the Computer component passes the results of each data() call
- * back to the originating component.
- *
- * WARNING: Since State objects are low-level objects that have no UI requirements, they do not
- * inherit from the Component class, so you should only use class methods of Component, such as
- * Component.assert() (or Debugger methods if the Debugger is available).
- *
- * NOTE: 1.01 is the first version to provide limited save/restore support using localStorage.
- * From that point on, care must be taken to insure that any new version that's incompatible with
- * previous localStorage data be released with a version number that is at least 1 greater,
- * since we're tagging the localStorage data with the integer portion of the version string.
- *
- * @constructor
- * @param {Component} component
- * @param {string} [sVersion] is used to append a major version number to the key
- * @param {string} [sSuffix] is used to append any additional suffixes to the key
- */
-function State(component, sVersion, sSuffix) {
- this.id = component.id;
- this.key = State.key(component, sVersion, sSuffix);
- this.dbg = component.dbg;
- this.unload(component.parms);
-}
-
-/**
- * State.key(component, sVersion, sSuffix)
- *
- * This encapsulates the key generation code.
- *
- * @param {Component} component
- * @param {string} [sVersion] is used to append a major version number to the key
- * @param {string} [sSuffix] is used to append any additional suffixes to the key
- * @return {string} key
- */
-State.key = function(component, sVersion, sSuffix) {
- var key = component.id;
- if (sVersion) {
- var i = sVersion.indexOf('.');
- if (i > 0) key += ".v" + sVersion.substr(0, i);
+class State {
+ /**
+ * State(component, sVersion, sSuffix)
+ *
+ * State objects are used by components to save/restore their state.
+ *
+ * During a save operation, components add data to a State object via set(), and then return
+ * the resulting data using data().
+ *
+ * During a restore operation, the Computer component passes the results of each data() call
+ * back to the originating component.
+ *
+ * WARNING: Since State objects are low-level objects that have no UI requirements, they do not
+ * inherit from the Component class, so you should only use class methods of Component, such as
+ * Component.assert() (or Debugger methods if the Debugger is available).
+ *
+ * NOTE: 1.01 is the first version to provide limited save/restore support using localStorage.
+ * From that point on, care must be taken to insure that any new version that's incompatible with
+ * previous localStorage data be released with a version number that is at least 1 greater,
+ * since we're tagging the localStorage data with the integer portion of the version string.
+ *
+ * @param {Component} component
+ * @param {string} [sVersion] is used to append a major version number to the key
+ * @param {string} [sSuffix] is used to append any additional suffixes to the key
+ */
+ constructor(component, sVersion, sSuffix)
+ {
+ this.id = component.id;
+ this.dbg = component.dbg;
+ this.json = "";
+ this.state = {};
+ this.fLoaded = this.fParsed = false;
+ this.key = State.key(component, sVersion, sSuffix);
+ this.unload(component.parms);
}
- if (sSuffix) {
- key += "." + sSuffix;
- }
- return key;
-};
-/**
- * State.compress(aSrc)
- *
- * @param {Array.|null} aSrc
- * @return {Array.|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
- */
-State.compress = function(aSrc) {
- if (aSrc) {
- var iSrc = 0;
- var iComp = 0;
- var aComp = [];
- while (iSrc < aSrc.length) {
- var n = aSrc[iSrc];
- Component.assert(n !== undefined);
- var iCompare = iSrc + 1;
- while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
- aComp[iComp++] = iCompare - iSrc;
- aComp[iComp++] = n;
- iSrc = iCompare;
- }
- if (aComp.length < aSrc.length) return aComp;
- }
- return aSrc;
-};
-
-/**
- * State.decompress(aComp)
- *
- * @param {Array.} aComp
- * @param {number} nLength is expected length of decompressed data
- * @return {Array.}
- */
-State.decompress = function(aComp, nLength) {
- var iDst = 0;
- var aDst = new Array(nLength);
- var iComp = 0;
- while (iComp < aComp.length - 1) {
- var c = aComp[iComp++];
- var n = aComp[iComp++];
- while (c--) {
- aDst[iDst++] = n;
- }
- }
- Component.assert(aDst.length == nLength);
- return aDst;
-};
-
-/**
- * State.compressEvenOdd(aSrc)
- *
- * This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
- * followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
- * addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
- * for compress(), but the best case for compressEvenOdd().
- *
- * One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
- * and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
- * and return an uninitialized array.
- *
- * @param {Array.|null} aSrc
- * @return {Array.|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
- */
-State.compressEvenOdd = function(aSrc) {
- if (aSrc) {
- var iComp = 0, aComp = [];
- if (aSrc[0] !== undefined) {
- for (var off = 0; off < 2; off++) {
- var iSrc = off;
- while (iSrc < aSrc.length) {
- var n = aSrc[iSrc];
- var iCompare = iSrc + 2;
- while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
- aComp[iComp++] = (iCompare - iSrc) >> 1;
- aComp[iComp++] = n;
- iSrc = iCompare;
- }
- }
- }
- if (aComp.length < aSrc.length) return aComp;
- }
- return aSrc;
-};
-
-/**
- * State.decompressEvenOdd(aComp, nLength)
- *
- * This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
- * that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
- * if you used even/odd compression, then you must use even/odd decompression.
- *
- * @param {Array.} aComp
- * @param {number} nLength is expected length of decompressed data
- * @return {Array.}
- */
-State.decompressEvenOdd = function(aComp, nLength) {
- var iDst = 0;
- var aDst = new Array(nLength);
- var iComp = 0;
- while (iComp < aComp.length - 1) {
- var c = aComp[iComp++];
- var n = aComp[iComp++];
- while (c--) {
- aDst[iDst] = n;
- iDst += 2;
- }
- /*
- * The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
- * the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
- * done.
- */
- Component.assert(iDst <= nLength || iComp == aComp.length);
- if (iDst == nLength) iDst = 1;
- }
- Component.assert(aDst.length == nLength);
- return aDst;
-};
-
-State.prototype = {
- constructor: State,
/**
* set(id, data)
*
@@ -213,13 +76,15 @@ State.prototype = {
* @param {number|string} id
* @param {Object|string} data
*/
- set: function(id, data) {
+ set(id, data)
+ {
try {
- this[this.id][id] = data;
+ this.state[id] = data;
} catch(e) {
Component.log(e.message);
}
- },
+ }
+
/**
* get(id)
*
@@ -227,43 +92,38 @@ State.prototype = {
* @param {number|string} id
* @return {Object|string|null}
*/
- get: function(id) {
- return this[this.id][id] || null;
- },
- /**
- * value()
- *
- * Use this instead of data() if you haven't called parse() yet.
- *
- * @this {State}
- * @return {string}
- */
- value: function() {
- return this[this.id];
- },
+ get(id)
+ {
+ return this.state[id] || null;
+ }
+
/**
* data()
*
* @this {State}
* @return {Object}
*/
- data: function() {
- return this[this.id];
- },
+ data()
+ {
+ return this.state;
+ }
+
/**
- * load(s)
+ * load(json)
*
* WARNING: Make sure you follow this call with either a call to parse() or unload(),
* because any stringified data that we've loaded isn't usable until it's been parsed.
*
* @this {State}
- * @param {Object|string|null} [s]
+ * @param {string|null} [json]
* @return {boolean} true if state exists in localStorage, false if not
*/
- load: function(s) {
- if (s) {
- this[this.id] = s;
+ load(json)
+ {
+ if (json) {
+ this.json = json;
this.fLoaded = true;
+ this.fParsed = false;
return true;
}
if (this.fLoaded) {
@@ -272,17 +132,18 @@ State.prototype = {
*/
return true;
}
- if (web.hasLocalStorage()) {
- s = web.getLocalStorageItem(this.key);
+ if (Web.hasLocalStorage()) {
+ var s = Web.getLocalStorageItem(this.key);
if (s) {
- this[this.id] = s;
+ this.json = s;
this.fLoaded = true;
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes loaded");
return true;
}
}
return false;
- },
+ }
+
/**
* parse()
*
@@ -293,27 +154,33 @@ State.prototype = {
* @this {State}
* @return {boolean} true if successful, false if error
*/
- parse: function() {
+ parse()
+ {
var fSuccess = true;
- try {
- this[this.id] = JSON.parse(this[this.id]);
- } catch (e) {
- Component.error(e.message || e);
- fSuccess = false;
+ if (!this.fParsed) {
+ try {
+ this.state = JSON.parse(this.json);
+ this.fParsed = true;
+ } catch (e) {
+ Component.error(e.message || e);
+ fSuccess = false;
+ }
}
return fSuccess;
- },
+ }
+
/**
* store()
*
* @this {State}
* @return {boolean} true if successful, false if error
*/
- store: function() {
+ store()
+ {
var fSuccess = true;
- if (web.hasLocalStorage()) {
- var s = JSON.stringify(this[this.id]);
- if (web.setLocalStorageItem(this.key, s)) {
+ if (Web.hasLocalStorage()) {
+ var s = JSON.stringify(this.state);
+ if (Web.setLocalStorageItem(this.key, s)) {
if (DEBUG) Component.log("localStorage(" + this.key + "): " + s.length + " bytes stored");
} else {
/*
@@ -327,20 +194,19 @@ State.prototype = {
}
}
return fSuccess;
- },
+ }
+
/**
* toString()
*
- * We can't know whether this might be called before parse() or after parse(), so we check.
- * If before, then this[this.id] will still be in string form; if after, it will be an Object.
- *
* @this {State}
* @return {string} JSON-encoded state
*/
- toString: function() {
- var value = this[this.id];
- return (typeof value == "string"? value : JSON.stringify(value));
- },
+ toString()
+ {
+ return this.state? JSON.stringify(this.state) : this.json;
+ }
+
/**
* unload(parms)
*
@@ -351,11 +217,14 @@ State.prototype = {
* @this {State}
* @param {Object} [parms]
*/
- unload: function(parms) {
- this[this.id] = {};
+ unload(parms)
+ {
+ this.json = "";
+ this.state = {};
+ this.fLoaded = this.fParsed = false;
if (parms) this.set("parms", parms);
- this.fLoaded = false;
- },
+ }
+
/**
* clear(fAll)
*
@@ -365,19 +234,164 @@ State.prototype = {
* @this {State}
* @param {boolean} [fAll] true to unconditionally clear ALL localStorage for the current domain
*/
- clear: function(fAll) {
+ clear(fAll)
+ {
this.unload();
- var aKeys = web.getLocalStorageKeys();
+ var aKeys = Web.getLocalStorageKeys();
for (var i = 0; i < aKeys.length; i++) {
var sKey = aKeys[i];
if (sKey && (fAll || sKey.substr(0, this.key.length) == this.key)) {
- web.removeLocalStorageItem(sKey);
+ Web.removeLocalStorageItem(sKey);
if (DEBUG) Component.log("localStorage(" + sKey + ") removed");
aKeys.splice(i, 1);
i = 0;
}
}
}
-};
+
+ /**
+ * State.key(component, sVersion, sSuffix)
+ *
+ * This encapsulates the key generation code.
+ *
+ * @param {Component} component
+ * @param {string} [sVersion] is used to append a major version number to the key
+ * @param {string} [sSuffix] is used to append any additional suffixes to the key
+ * @return {string} key
+ */
+ static key(component, sVersion, sSuffix)
+ {
+ var key = component.id;
+ if (sVersion) {
+ var i = sVersion.indexOf('.');
+ if (i > 0) key += ".v" + sVersion.substr(0, i);
+ }
+ if (sSuffix) {
+ key += "." + sSuffix;
+ }
+ return key;
+ }
+
+ /**
+ * State.compress(aSrc)
+ *
+ * @param {Array.|null} aSrc
+ * @return {Array.|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
+ */
+ static compress(aSrc)
+ {
+ if (aSrc) {
+ var iSrc = 0;
+ var iComp = 0;
+ var aComp = [];
+ while (iSrc < aSrc.length) {
+ var n = aSrc[iSrc];
+ Component.assert(n !== undefined);
+ var iCompare = iSrc + 1;
+ while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare++;
+ aComp[iComp++] = iCompare - iSrc;
+ aComp[iComp++] = n;
+ iSrc = iCompare;
+ }
+ if (aComp.length < aSrc.length) return aComp;
+ }
+ return aSrc;
+ }
+
+ /**
+ * State.decompress(aComp)
+ *
+ * @param {Array.} aComp
+ * @param {number} nLength is expected length of decompressed data
+ * @return {Array.}
+ */
+ static decompress(aComp, nLength)
+ {
+ var iDst = 0;
+ var aDst = new Array(nLength);
+ var iComp = 0;
+ while (iComp < aComp.length - 1) {
+ var c = aComp[iComp++];
+ var n = aComp[iComp++];
+ while (c--) {
+ aDst[iDst++] = n;
+ }
+ }
+ Component.assert(aDst.length == nLength);
+ return aDst;
+ }
+
+ /**
+ * State.compressEvenOdd(aSrc)
+ *
+ * This is a very simple variation on compress() that compresses all the EVEN elements of aSrc first,
+ * followed by all the ODD elements. This tends to work better on EGA video memory, because when odd/even
+ * addressing is enabled (eg, for text modes), the DWORD values tend to alternate, which is the worst case
+ * for compress(), but the best case for compressEvenOdd().
+ *
+ * One wrinkle we support: if the first element is uninitialized, then we assume the entire array is undefined,
+ * and return an empty compressed array. Conversely, decompressEvenOdd() will take an empty compressed array
+ * and return an uninitialized array.
+ *
+ * @param {Array.|null} aSrc
+ * @return {Array.|null} is either the original array (aSrc), or a smaller array of "count, value" pairs (aComp)
+ */
+ static compressEvenOdd(aSrc)
+ {
+ if (aSrc) {
+ var iComp = 0, aComp = [];
+ if (aSrc[0] !== undefined) {
+ for (var off = 0; off < 2; off++) {
+ var iSrc = off;
+ while (iSrc < aSrc.length) {
+ var n = aSrc[iSrc];
+ var iCompare = iSrc + 2;
+ while (iCompare < aSrc.length && aSrc[iCompare] === n) iCompare += 2;
+ aComp[iComp++] = (iCompare - iSrc) >> 1;
+ aComp[iComp++] = n;
+ iSrc = iCompare;
+ }
+ }
+ }
+ if (aComp.length < aSrc.length) return aComp;
+ }
+ return aSrc;
+ }
+
+ /**
+ * State.decompressEvenOdd(aComp, nLength)
+ *
+ * This is the counterpart to compressEvenOdd(). Note that because there's nothing in the compressed sequence
+ * that differentiates a compress() sequence from a compressEvenOdd() sequence, you simply have to be consistent:
+ * if you used even/odd compression, then you must use even/odd decompression.
+ *
+ * @param {Array.} aComp
+ * @param {number} nLength is expected length of decompressed data
+ * @return {Array.}
+ */
+ static decompressEvenOdd(aComp, nLength)
+ {
+ var iDst = 0;
+ var aDst = new Array(nLength);
+ var iComp = 0;
+ while (iComp < aComp.length - 1) {
+ var c = aComp[iComp++];
+ var n = aComp[iComp++];
+ while (c--) {
+ aDst[iDst] = n;
+ iDst += 2;
+ }
+ /*
+ * The output of a "count,value" pair will never exceed the end of the output array, so as soon as we reach it
+ * the first time, we know it's time to switch to ODD elements, and as soon as we reach it again, we should be
+ * done.
+ */
+ Component.assert(iDst <= nLength || iComp == aComp.length);
+ if (iDst == nLength) iDst = 1;
+ }
+ Component.assert(aDst.length == nLength);
+ return aDst;
+ }
+}
if (NODE) module.exports = State;
diff --git a/modules/shared/lib/strlib.js b/modules/shared/lib/strlib.js
index 7ac90e98d..a9dbf0bf8 100644
--- a/modules/shared/lib/strlib.js
+++ b/modules/shared/lib/strlib.js
@@ -28,422 +28,545 @@
"use strict";
-var str = {};
+class Str {
+ /**
+ * isValidInt(s, base)
+ *
+ * The built-in parseInt() function has the annoying feature of returning a partial value (ie,
+ * up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf.
+ *
+ * So it's best to use our own Str.parseInt() function, which will in turn use this function to
+ * validate the entire string.
+ *
+ * @param {string} s is the string representation of some number
+ * @param {number} [base] is the radix to use (default is 10); only 2, 8, 10 and 16 are supported
+ * @return {boolean} true if valid, false if invalid (or the specified base isn't supported)
+ */
+ static isValidInt(s, base)
+ {
+ if (!base || base == 10) return s.match(/^[0-9]+$/) !== null;
+ if (base == 16) return s.match(/^[0-9a-f]+$/i) !== null;
+ if (base == 8) return s.match(/^[0-7]+$/) !== null;
+ if (base == 2) return s.match(/^[01]+$/) !== null;
+ return false;
+ }
-/**
- * isValidInt(s, base)
- *
- * The built-in parseInt() function has the annoying feature of returning a partial value (ie,
- * up to the point where it encounters an invalid character); eg, parseInt("foo", 16) returns 0xf.
- *
- * So it's best to use our own str.parseInt() function, which will in turn use this function to
- * validate the entire string.
- *
- * @param {string} s is the string representation of some number
- * @param {number} [base] is the radix to use (default is 10); only 2, 8, 10 and 16 are supported
- * @return {boolean} true if valid, false if invalid (or the specified base isn't supported)
- */
-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;
- if (base == 8) return s.match(/^[0-7]+$/) !== null;
- if (base == 2) return s.match(/^[01]+$/) !== null;
- return false;
-};
+ /**
+ * parseInt(s, base)
+ *
+ * This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes
+ * ('$' or "0x" for hex, '#' or "0o" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for
+ * binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values;
+ * see isValidInt() for details.
+ *
+ * The use of multiple prefix/suffix combinations is undefined (although for the record, we process
+ * prefixes first). We do NOT support the "0b" prefix to indicate binary UNLESS one or more commas are
+ * also present (because "0b" is also a valid hex sequence), and we do NOT support a single leading zero
+ * to indicate octal (because such a number could also be decimal or hex). Any number of commas are
+ * allowed; we remove them all before calling the built-in parseInt().
+ *
+ * To summarize our non-standard alternatives: a 'y' suffix indicates binary, a '#' prefix indicates
+ * octal, a '$' prefix indicates hex, and a "0b" prefix indicates binary IF at least one comma is present.
+ * Commas are useful for grouping binary digits, but if you don't want to use them, then you must use a
+ * 'y' suffix for binary numbers.
+ *
+ * @param {string} s is the string representation of some number
+ * @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes
+ * @return {number|undefined} corresponding value, or undefined if invalid
+ */
+ static parseInt(s, base)
+ {
+ var value;
-/**
- * parseInt(s, base)
- *
- * This is a wrapper around the built-in parseInt() function. Our wrapper recognizes certain prefixes
- * ('$' or "0x" for hex, '#' or "0o" for octal) and suffixes ('.' for decimal, 'h' for hex, 'y' for
- * binary), and then calls isValidInt() to ensure we don't convert strings that contain partial values;
- * see isValidInt() for details.
- *
- * The use of multiple prefix/suffix combinations is undefined (although for the record, we process
- * prefixes first). We do NOT support the "0b" prefix to indicate binary UNLESS one or more commas are
- * also present (because "0b" is also a valid hex sequence), and we do NOT support a single leading zero
- * to indicate octal (because such a number could also be decimal or hex). Any number of commas are
- * allowed; we remove them all before calling the built-in parseInt().
- *
- * To summarize our non-standard alternatives: a 'y' suffix indicates binary, a '#' prefix indicates
- * octal, a '$' prefix indicates hex, and a "0b" prefix indicates binary IF at least one comma is present.
- * Commas are useful for grouping binary digits, but if you don't want to use them, then you must use a
- * 'y' suffix for binary numbers.
- *
- * @param {string} s is the string representation of some number
- * @param {number} [base] is the radix to use (default is 10); can be overridden by prefixes/suffixes
- * @return {number|undefined} corresponding value, or undefined if invalid
- */
-str.parseInt = function(s, base)
-{
- var value;
-
- if (s) {
- if (!base) base = 10;
- var chPrefix = s.charAt(0);
- var fCommas = (s.indexOf(',') > 0);
- if (fCommas) s = s.replace(/,/g, '');
- if (chPrefix == '#') {
- base = 8;
- chPrefix = null;
- }
- else if (chPrefix == '$') {
- base = 16;
- chPrefix = null;
- }
- if (chPrefix == null) {
- s = s.substr(1);
- }
- else {
- if (chPrefix == '0') {
- chPrefix = s.charAt(1);
- if (chPrefix == 'b' && fCommas) {
- base = 2;
- chPrefix = null;
- }
- if (chPrefix == 'o') {
- base = 8;
- chPrefix = null;
- }
- else if (chPrefix == 'x') {
- base = 16;
- chPrefix = null;
- }
+ if (s) {
+ if (!base) base = 10;
+ var chPrefix = s.charAt(0);
+ var fCommas = (s.indexOf(',') > 0);
+ if (fCommas) s = s.replace(/,/g, '');
+ if (chPrefix == '#') {
+ base = 8;
+ chPrefix = null;
+ }
+ else if (chPrefix == '$') {
+ base = 16;
+ chPrefix = null;
}
if (chPrefix == null) {
- s = s.substr(2);
+ s = s.substr(1);
}
else {
- var chSuffix = s.charAt(s.length-1).toLowerCase();
- if (chSuffix == 'y') {
- base = 2;
- chSuffix = null;
+ if (chPrefix == '0') {
+ chPrefix = s.charAt(1);
+ if (chPrefix == 'b' && fCommas) {
+ base = 2;
+ chPrefix = null;
+ }
+ if (chPrefix == 'o') {
+ base = 8;
+ chPrefix = null;
+ }
+ else if (chPrefix == 'x') {
+ base = 16;
+ chPrefix = null;
+ }
}
- else if (chSuffix == '.') {
- base = 10;
- chSuffix = null;
+ if (chPrefix == null) {
+ s = s.substr(2);
}
- else if (chSuffix == 'h') {
- base = 16;
- chSuffix = null;
+ else {
+ var chSuffix = s.charAt(s.length - 1).toLowerCase();
+ if (chSuffix == 'y') {
+ base = 2;
+ chSuffix = null;
+ }
+ else if (chSuffix == '.') {
+ base = 10;
+ chSuffix = null;
+ }
+ else if (chSuffix == 'h') {
+ base = 16;
+ chSuffix = null;
+ }
+ if (chSuffix == null) s = s.substr(0, s.length - 1);
}
- if (chSuffix == null) s = s.substr(0, s.length-1);
+ }
+ var v;
+ if (Str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
+ value = v | 0;
}
}
- var v;
- if (str.isValidInt(s, base) && !isNaN(v = parseInt(s, base))) {
- value = v|0;
- }
+ return value;
}
- return value;
-};
-/**
- * toBin(n, cch, grouping)
- *
- * Converts an integer to binary, with the specified number of digits (up to the default of 32).
- *
- * @param {number|null|undefined} n is a 32-bit value
- * @param {number} [cch] is the desired number of binary digits (32 is both the default and the maximum)
- * @param {number} [grouping]
- * @return {string} the binary representation of n
- */
-str.toBin = function(n, cch, grouping)
-{
- var s = "";
- if (!cch) {
- cch = 32;
- } else {
- if (cch > 32) cch = 32;
- }
- /*
- * An initial "falsey" check for null takes care of both null and undefined;
- * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
+ /**
+ * toBin(n, cch, grouping)
*
- * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
- * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
- * values displayed differently.
- */
- var fInvalid = (n == null || isNaN(n));
- var group = (grouping = grouping || cch);
- while (cch-- > 0) {
- if (!group) {
- s = "," + s;
- group = grouping;
- }
- s = (fInvalid? '?' : ((n & 0x1)? '1' : '0')) + s;
- n >>= 1;
- group--;
- }
- return s;
-};
-
-/**
- * toBinBytes(n, cb, fPrefix)
- *
- * Converts an integer to binary, with the specified number of bytes (up to the default of 4).
- *
- * @param {number|null|undefined} n is a 32-bit value
- * @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum)
- * @param {boolean} [fPrefix]
- * @return {string} the binary representation of n
- */
-str.toBinBytes = function(n, cb, fPrefix)
-{
- var s = "";
- if (!cb || cb > 4) cb = 4;
- for (var i = 0; i < cb; i++) {
- if (s) s = ',' + s;
- s = str.toBin(n & 0xff, 8) + s;
- n >>= 8;
- }
- return (fPrefix? "0b" : "") + s;
-};
-
-/**
- * toOct(n, cch, fPrefix)
- *
- * Converts an integer to octal, with the specified number of digits (default of 6; max of 11)
- *
- * You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it
- * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw
- * an exception, whereas this function will return '?' characters.
- *
- * @param {number|null|undefined} n is a 32-bit value
- * @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6 or 11)
- * @param {boolean} [fPrefix]
- * @return {string} the octal representation of n
- */
-str.toOct = function(n, cch, fPrefix)
-{
- var s = "";
-
- if (cch) {
- if (cch > 11) cch = 11;
- } else {
- cch = (n & ~0xffffff)? 11 : ((n & ~0xffff)? 8 : 6);
- }
- /*
- * An initial "falsey" check for null takes care of both null and undefined;
- * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
+ * Converts an integer to binary, with the specified number of digits (up to the default of 32).
*
- * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
- * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
- * values displayed differently.
+ * @param {number|null|undefined} n is a 32-bit value
+ * @param {number} [cch] is the desired number of binary digits (32 is both the default and the maximum)
+ * @param {number} [grouping]
+ * @return {string} the binary representation of n
*/
- if (n == null || isNaN(n)) {
- while (cch-- > 0) s = '?' + s;
- } else {
+ static toBin(n, cch, grouping)
+ {
+ var s = "";
+ if (!cch) {
+ cch = 32;
+ } else {
+ if (cch > 32) cch = 32;
+ }
+ /*
+ * An initial "falsey" check for null takes care of both null and undefined;
+ * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
+ *
+ * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
+ * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
+ * values displayed differently.
+ */
+ var fInvalid = (n == null || isNaN(n));
+ var group = (grouping = grouping || cch);
while (cch-- > 0) {
- var d = (n & 7) + 0x30;
- s = String.fromCharCode(d) + s;
- n >>= 3;
+ if (!group) {
+ s = "," + s;
+ group = grouping;
+ }
+ s = (fInvalid ? '?' : ((n & 0x1) ? '1' : '0')) + s;
+ n >>= 1;
+ group--;
}
+ return s;
}
- return (fPrefix? "0o" : "") + s;
-};
-/**
- * toDec(n, cch)
- *
- * Converts an integer to decimal, with the specified number of digits (default of 5; max of 10)
- *
- * You might be tempted to use the built-in n.toString(10) instead, but it doesn't zero-pad and it
- * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw
- * an exception, whereas this function will return '?' characters.
- *
- * @param {number|null|undefined} n is a 32-bit value
- * @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 10)
- * @return {string} the octal representation of n
- */
-str.toDec = function(n, cch)
-{
- var s = "";
-
- if (cch) {
- if (cch > 10) cch = 10;
- } else {
- cch = (n & ~0xffff)? 10 : 5;
- }
- /*
- * An initial "falsey" check for null takes care of both null and undefined;
- * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
+ /**
+ * toBinBytes(n, cb, fPrefix)
*
- * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
- * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
- * values displayed differently.
- */
- if (n == null || isNaN(n)) {
- while (cch-- > 0) s = '?' + s;
- } else {
- while (cch-- > 0) {
- var d = (n % 10) + 0x30;
- s = String.fromCharCode(d) + s;
- n /= 10;
- }
- }
- return s;
-};
-
-/**
- * toHex(n, cch, fPrefix)
- *
- * Converts an integer to hex, with the specified number of digits (default of 4 or 8, max 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 this function will 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 null/undefined/NaN values:
- *
- * s = (n < 0? n + 0x100000000 : n).toString(16);
- * s = "00000000".substr(0, 8 - s.length) + s;
- * s = s.substr(0, cch).toUpperCase();
- *
- * @param {number|null|undefined} n is a 32-bit value
- * @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4 or 8)
- * @param {boolean} [fPrefix]
- * @return {string} the hex representation of n
- */
-str.toHex = function(n, cch, fPrefix)
-{
- var s = "";
-
- if (cch) {
- if (cch > 8) cch = 8;
- } else {
- cch = (n & ~0xffff)? 8 : 4;
- }
- /*
- * An initial "falsey" check for null takes care of both null and undefined;
- * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
+ * Converts an integer to binary, with the specified number of bytes (up to the default of 4).
*
- * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
- * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
- * values displayed differently.
+ * @param {number|null|undefined} n is a 32-bit value
+ * @param {number} [cb] is the desired number of binary bytes (4 is both the default and the maximum)
+ * @param {boolean} [fPrefix]
+ * @return {string} the binary representation of n
*/
- if (n == null || isNaN(n)) {
- 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;
+ static toBinBytes(n, cb, fPrefix)
+ {
+ var s = "";
+ if (!cb || cb > 4) cb = 4;
+ for (var i = 0; i < cb; i++) {
+ if (s) s = ',' + s;
+ s = Str.toBin(n & 0xff, 8) + s;
+ n >>= 8;
}
+ return (fPrefix ? "0b" : "") + s;
}
- return (fPrefix? "0x" : "") + s;
-};
-/**
- * toHexByte(b)
- *
- * Alias for str.toHex(b, 2, true)
- *
- * @param {number|null|undefined} b is a byte value
- * @return {string} the hex representation of b
- */
-str.toHexByte = function(b)
-{
- return str.toHex(b, 2, true);
-};
-
-/**
- * toHexWord(w)
- *
- * Alias for str.toHex(w, 4, true)
- *
- * @param {number|null|undefined} w is a word (16-bit) value
- * @return {string} the hex representation of w
- */
-str.toHexWord = function(w)
-{
- return str.toHex(w, 4, true);
-};
-
-/**
- * toHexLong(l)
- *
- * Alias for str.toHex(l, 8, true)
- *
- * @param {number|null|undefined} l is a dword (32-bit) value
- * @return {string} the hex representation of w
- */
-str.toHexLong = function(l)
-{
- return str.toHex(l, 8, true);
-};
-
-/**
- * getBaseName(sFileName, fStripExt)
- *
- * This is a poor-man's version of Node's path.basename(), which Node-only components should use instead.
- *
- * Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips 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);
-
- /*
- * This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters.
+ /**
+ * toOct(n, cch, fPrefix)
+ *
+ * Converts an integer to octal, with the specified number of digits (default of 6; max of 11)
+ *
+ * You might be tempted to use the built-in n.toString(8) instead, but it doesn't zero-pad and it
+ * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw
+ * an exception, whereas this function will return '?' characters.
+ *
+ * @param {number|null|undefined} n is a 32-bit value
+ * @param {number} [cch] is the desired number of octal digits (0 or undefined for default of either 6 or 11)
+ * @param {boolean} [fPrefix]
+ * @return {string} the octal representation of n
*/
- i = sBaseName.indexOf('&');
- if (i > 0) sBaseName = sBaseName.substr(0, i);
+ static toOct(n, cch, fPrefix)
+ {
+ var s = "";
- if (fStripExt) {
- i = sBaseName.lastIndexOf(".");
- if (i > 0) {
- sBaseName = sBaseName.substring(0, i);
+ if (cch) {
+ if (cch > 11) cch = 11;
+ } else {
+ cch = (n & ~0xffffff) ? 11 : ((n & ~0xffff) ? 8 : 6);
}
+ /*
+ * An initial "falsey" check for null takes care of both null and undefined;
+ * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
+ *
+ * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
+ * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
+ * values displayed differently.
+ */
+ if (n == null || isNaN(n)) {
+ while (cch-- > 0) s = '?' + s;
+ } else {
+ while (cch-- > 0) {
+ var d = (n & 7) + 0x30;
+ s = String.fromCharCode(d) + s;
+ n >>= 3;
+ }
+ }
+ return (fPrefix ? "0o" : "") + s;
}
- 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();
+ /**
+ * toDec(n, cch)
+ *
+ * Converts an integer to decimal, with the specified number of digits (default of 5; max of 10)
+ *
+ * You might be tempted to use the built-in n.toString(10) instead, but it doesn't zero-pad and it
+ * doesn't properly convert negative values. Moreover, if n is undefined, n.toString() will throw
+ * an exception, whereas this function will return '?' characters.
+ *
+ * @param {number|null|undefined} n is a 32-bit value
+ * @param {number} [cch] is the desired number of decimal digits (0 or undefined for default of either 5 or 10)
+ * @return {string} the octal representation of n
+ */
+ static toDec(n, cch)
+ {
+ var s = "";
+
+ if (cch) {
+ if (cch > 10) cch = 10;
+ } else {
+ cch = (n & ~0xffff) ? 10 : 5;
+ }
+ /*
+ * An initial "falsey" check for null takes care of both null and undefined;
+ * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
+ *
+ * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
+ * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
+ * values displayed differently.
+ */
+ if (n == null || isNaN(n)) {
+ while (cch-- > 0) s = '?' + s;
+ } else {
+ while (cch-- > 0) {
+ var d = (n % 10) + 0x30;
+ s = String.fromCharCode(d) + s;
+ n /= 10;
+ }
+ }
+ return s;
}
- 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;
-};
+ /**
+ * toHex(n, cch, fPrefix)
+ *
+ * Converts an integer to hex, with the specified number of digits (default of 4 or 8, max 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 this function will 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 null/undefined/NaN values:
+ *
+ * s = (n < 0? n + 0x100000000 : n).toString(16);
+ * s = "00000000".substr(0, 8 - s.length) + s;
+ * s = s.substr(0, cch).toUpperCase();
+ *
+ * @param {number|null|undefined} n is a 32-bit value
+ * @param {number} [cch] is the desired number of hex digits (0 or undefined for default of either 4 or 8)
+ * @param {boolean} [fPrefix]
+ * @return {string} the hex representation of n
+ */
+ static toHex(n, cch, fPrefix)
+ {
+ var s = "";
-str.aHTMLEscapeMap = {
+ if (cch) {
+ if (cch > 8) cch = 8;
+ } else {
+ cch = (n & ~0xffff) ? 8 : 4;
+ }
+ /*
+ * An initial "falsey" check for null takes care of both null and undefined;
+ * we can't rely entirely on isNaN(), because isNaN(null) returns false, oddly enough.
+ *
+ * Alternatively, we could mask and shift n regardless of whether it's null/undefined/NaN,
+ * since JavaScript coerces such operands to zero, but I think there's "value" in seeing those
+ * values displayed differently.
+ */
+ if (n == null || isNaN(n)) {
+ 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 (fPrefix ? "0x" : "") + s;
+ }
+
+ /**
+ * toHexByte(b)
+ *
+ * Alias for Str.toHex(b, 2, true)
+ *
+ * @param {number|null|undefined} b is a byte value
+ * @return {string} the hex representation of b
+ */
+ static toHexByte(b)
+ {
+ return Str.toHex(b, 2, true);
+ }
+
+ /**
+ * toHexWord(w)
+ *
+ * Alias for Str.toHex(w, 4, true)
+ *
+ * @param {number|null|undefined} w is a word (16-bit) value
+ * @return {string} the hex representation of w
+ */
+ static toHexWord(w)
+ {
+ return Str.toHex(w, 4, true);
+ }
+
+ /**
+ * toHexLong(l)
+ *
+ * Alias for Str.toHex(l, 8, true)
+ *
+ * @param {number|null|undefined} l is a dword (32-bit) value
+ * @return {string} the hex representation of w
+ */
+ static toHexLong(l)
+ {
+ return Str.toHex(l, 8, true);
+ }
+
+ /**
+ * getBaseName(sFileName, fStripExt)
+ *
+ * This is a poor-man's version of Node's path.basename(), which Node-only components should use instead.
+ *
+ * Note that if fStripExt is true, this strips ANY extension, whereas path.basename() strips 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}
+ */
+ static getBaseName(sFileName, fStripExt)
+ {
+ var sBaseName = sFileName;
+
+ var i = sFileName.lastIndexOf('/');
+ if (i >= 0) sBaseName = sFileName.substr(i + 1);
+
+ /*
+ * This next bit is a kludge to clean up names that are part of a URL that includes unsightly query parameters.
+ */
+ i = sBaseName.indexOf('&');
+ if (i > 0) sBaseName = sBaseName.substr(0, i);
+
+ 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
+ */
+ static getExtension(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
+ */
+ static endsWith(s, sSuffix)
+ {
+ return s.indexOf(sSuffix, s.length - sSuffix.length) !== -1;
+ }
+
+ /**
+ * escapeHTML(sHTML)
+ *
+ * @param {string} sHTML
+ * @return {string} with HTML entities "escaped", similar to PHP's htmlspecialchars()
+ */
+ static escapeHTML(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}
+ */
+ static replaceAll(sFind, sReplace, s)
+ {
+ var a = {};
+ a[sFind] = sReplace;
+ return Str.replaceArray(a, s);
+ }
+
+ /**
+ * replaceArray(a, s)
+ *
+ * @param {Object} a
+ * @param {string} s
+ * @return {string}
+ */
+ static replaceArray(a, s)
+ {
+ var sMatch = "";
+ for (var k in a) {
+ /*
+ * As noted in:
+ *
+ * http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/
+ *
+ * inside character classes, only backslash, caret, hyphen and the closing bracket need to be
+ * escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first,
+ * and the hyphen is last, you can avoid escaping those as well.
+ */
+ k = k.replace(/([\\[\]*{}().+?])/g, "\\$1");
+ sMatch += (sMatch ? '|' : '') + k;
+ }
+ return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m)
+ {
+ return a[m];
+ });
+ }
+
+ /**
+ * pad(s, cch, fPadLeft)
+ *
+ * NOTE: the maximum amount of padding currently supported is 40 spaces.
+ *
+ * @param {string} s is a string
+ * @param {number} cch is desired length
+ * @param {boolean} [fPadLeft] (default is padding on the right)
+ * @return {string} the original string (s) with spaces padding it to the specified length
+ */
+ static pad(s, cch, fPadLeft)
+ {
+ var sPadding = " ";
+ return fPadLeft ? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch);
+ }
+
+ /**
+ * stripLeadingZeros(s, fPad)
+ *
+ * @param {string} s
+ * @param {boolean} [fPad]
+ * @return {string}
+ */
+ static stripLeadingZeros(s, fPad)
+ {
+ var cch = s.length;
+ s = s.replace(/^0+([0-9A-F]+)$/i, "$1");
+ if (fPad) s = Str.pad(s, cch, true);
+ return s;
+ }
+
+ /**
+ * trim(s)
+ *
+ * @param {string} s
+ * @return {string}
+ */
+ static trim(s)
+ {
+ if (String.prototype.trim) {
+ return s.trim();
+ }
+ return s.replace(/^\s+|\s+$/g, "");
+ }
+
+ /**
+ * toASCIICode(b)
+ *
+ * @param {number} b
+ * @return {string}
+ */
+ static toASCIICode(b)
+ {
+ var s;
+ if (b != Str.ASCII.CR && b != Str.ASCII.LF) {
+ s = Str.aASCIICodes[b];
+ }
+ if (s) {
+ s = '<' + s + '>';
+ } else {
+ s = String.fromCharCode(b);
+ }
+ return s;
+ }
+}
+
+Str.aHTMLEscapeMap = {
'&': '&',
'<': '<',
'>': '>',
@@ -451,111 +574,10 @@ 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) {
- /*
- * As noted in:
- *
- * http://www.regexguru.com/2008/04/escape-characters-only-when-necessary/
- *
- * inside character classes, only backslash, caret, hyphen and the closing bracket need to be
- * escaped. And in fact, if you ensure that the closing bracket is first, the caret is not first,
- * and the hyphen is last, you can avoid escaping those as well.
- */
- k = k.replace(/([\\[\]*{}().+?])/g, "\\$1");
- sMatch += (sMatch? '|' : '') + k;
- }
- return s.replace(new RegExp('(' + sMatch + ')', "g"), function(m) {
- return a[m];
- });
-};
-
-/**
- * pad(s, cch, fPadLeft)
- *
- * NOTE: the maximum amount of padding currently supported is 40 spaces.
- *
- * @param {string} s is a string
- * @param {number} cch is desired length
- * @param {boolean} [fPadLeft] (default is padding on the right)
- * @return {string} the original string (s) with spaces padding it to the specified length
- */
-str.pad = function(s, cch, fPadLeft)
-{
- var sPadding = " ";
- return fPadLeft? (sPadding + s).slice(-cch) : (s + sPadding).slice(0, cch);
-};
-
-/**
- * stripLeadingZeros(s, fPad)
- *
- * @param {string} s
- * @param {boolean} [fPad]
- * @return {string}
- */
-str.stripLeadingZeros = function(s, fPad)
-{
- var cch = s.length;
- s = s.replace(/^0+([0-9A-F]+)$/i, "$1");
- if (fPad) s = str.pad(s, cch, true);
- return s;
-};
-
-/**
- * trim(s)
- *
- * @param {string} s
- * @return {string}
- */
-str.trim = function(s)
-{
- if (String.prototype.trim) {
- return s.trim();
- }
- return s.replace(/^\s+|\s+$/g, "");
-};
-
/*
* Future home of a general-purpose ASCII table. TODO: Flesh it out.
*/
-str.ASCII = {
+Str.ASCII = {
LF: 0x0A,
CR: 0x0D
};
@@ -563,7 +585,7 @@ str.ASCII = {
/*
* Table for converting "unprintable" ASCII codes into mnemonics, to more clearly see what's being printed.
*/
-str.aASCIICodes = {
+Str.aASCIICodes = {
0x00: "NUL",
0x01: "SOH", // (CTRL_A) Start of Heading
0x02: "STX", // (CTRL_B) Start of Text
@@ -598,21 +620,16 @@ str.aASCIICodes = {
0x1F: "US" // Unit Separator
};
-/**
- * toASCIICode(b)
- *
- * @param {number} b
- * @return {string}
- */
-str.toASCIICode = function(b)
-{
- var s = (b != str.ASCII.CR && b != str.ASCII.LF? str.aASCIICodes[b] : null);
- if (s) {
- s = '<' + s + '>';
- } else {
- s = String.fromCharCode(b);
- }
- return s;
+Str.TYPES = {
+ NULL: 0,
+ BYTE: 1,
+ WORD: 2,
+ DWORD: 3,
+ NUMBER: 4,
+ STRING: 5,
+ BOOLEAN: 6,
+ OBJECT: 7,
+ ARRAY: 8
};
-if (NODE) module.exports = str;
+if (NODE) module.exports = Str;
diff --git a/modules/shared/lib/userapi.js b/modules/shared/lib/userapi.js
index 312405cce..1a24b3a77 100644
--- a/modules/shared/lib/userapi.js
+++ b/modules/shared/lib/userapi.js
@@ -33,7 +33,6 @@
*
* web.getHost() + UserAPI.ENDPOINT + '?' + UserAPI.QUERY.REQ + '=' + UserAPI.REQ.VERIFY + '&' + UserAPI.QUERY.USER + '=' + sUser;
*/
-
var UserAPI = {
ENDPOINT: "/api/v1/user",
QUERY: {
diff --git a/modules/shared/lib/usrlib.js b/modules/shared/lib/usrlib.js
index b6562e240..b76b46e78 100644
--- a/modules/shared/lib/usrlib.js
+++ b/modules/shared/lib/usrlib.js
@@ -28,181 +28,6 @@
"use strict";
-var usr = {};
-
-/**
- * binarySearch(a, v, fnCompare)
- *
- * @param {Array} a is an array
- * @param {number|string|Array|Object} v
- * @param {function((number|string|Array|Object), (number|string|Array|Object))} [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|Array|Object), (number|string|Array|Object))} [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 = Date.now || function() { return +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;
-};
-
/**
* @typedef {{
* mask: number,
@@ -216,100 +41,279 @@ var BitField;
*/
var BitFields;
-/**
- * defineBitFields(bfs)
- *
- * Prepares a bit field definition for use with getBitField() and setBitField(); eg:
- *
- * var bfs = usr.defineBitFields({num:20, count:8, btmod:1, type:3});
- *
- * The above defines a set of bit fields containg four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31).
- *
- * usr.setBitField(bfs.num, n, 1);
- *
- * The above set bit field "bfs.num" in numeric variable "n" to the value 1.
- *
- * @param {Object} bfs
- * @return {BitFields}
- */
-usr.defineBitFields = function(bfs)
-{
- var bit = 0;
- for (var f in bfs) {
- var width = bfs[f];
- var mask = ((1 << width) - 1) << bit;
- bfs[f] = {mask: mask, shift: bit};
- bit += width;
+class Usr {
+ /**
+ * binarySearch(a, v, fnCompare)
+ *
+ * @param {Array} a is an array
+ * @param {number|string|Array|Object} v
+ * @param {function((number|string|Array|Object), (number|string|Array|Object))} [fnCompare]
+ * @return {number} the index of matching entry if non-negative, otherwise the index of the insertion point
+ */
+ static binarySearch(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;
}
- // Component.assert(bit <= 32);
- return bfs;
-};
-/**
- * initBitFields(bfs, ...)
- *
- * @param {BitFields} bfs
- * @param {...number} var_args
- * @return {number} a value containing all supplied bit fields
- */
-usr.initBitFields = function(bfs, var_args)
-{
- var v = 0, i = 1;
- for (var f in bfs) {
- if (i >= arguments.length) break;
- v = usr.setBitField(bfs[f], v, arguments[i++]);
+ /**
+ * 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|Array|Object), (number|string|Array|Object))} [fnCompare]
+ */
+ static binaryInsert(a, v, fnCompare)
+ {
+ var index = Usr.binarySearch(a, v, fnCompare);
+ if (index < 0) {
+ a.splice(-(index + 1), 0, v);
+ }
}
- return v;
-};
-/**
- * getBitField(bf, v)
- *
- * @param {BitField} bf
- * @param {number} v is a value containing bit fields
- * @return {number} the value of the bit field in v defined by bf
- */
-usr.getBitField = function(bf, v)
-{
- return (v & bf.mask) >> bf.shift;
-};
-
-/**
- * setBitField(bf, v, n)
- *
- * @param {BitField} bf
- * @param {number} v is a value containing bit fields
- * @param {number} n is a value to store in v in the bit field defined by bf
- * @return {number} updated v
- */
-usr.setBitField = function(bf, v, n)
-{
- // Component.assert(!(n & ~(bf.mask >>> bf.shift)));
- return (v & ~bf.mask) | ((n << bf.shift) & bf.mask);
-};
-
-/**
- * indexOf(a, t, i)
- *
- * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it.
- *
- * @param {Array} a
- * @param {*} t
- * @param {number} [i]
- * @returns {number}
- */
-usr.indexOf = function(a, t, i)
-{
- if (Array.prototype.indexOf) {
- return a.indexOf(t, i);
+ /**
+ * getTimestamp()
+ *
+ * @return {string} timestamp containing the current date and time ("yyyy-mm-dd hh:mm:ss")
+ */
+ static getTimestamp()
+ {
+ 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());
}
- i = i || 0;
- if (i < 0) i += a.length;
- if (i < 0) i = 0;
- for (var n = a.length; i < n; i++) {
- if (i in a && a[i] === t) return i;
- }
- return -1;
-};
-if (NODE) module.exports = usr;
+ /**
+ * 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
+ */
+ static getMonthDays(nMonth, nYear)
+ {
+ var nDays = Usr.aMonthDays[nMonth - 1];
+ if (nDays == 28) {
+ if ((nYear % 4) === 0 && ((nYear % 100) || (nYear % 400) === 0)) {
+ nDays++;
+ }
+ }
+ return nDays;
+ }
+
+ /**
+ * 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
+ */
+ static formatDate(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;
+ }
+
+ /**
+ * defineBitFields(bfs)
+ *
+ * Prepares a bit field definition for use with getBitField() and setBitField(); eg:
+ *
+ * var bfs = Usr.defineBitFields({num:20, count:8, btmod:1, type:3});
+ *
+ * The above defines a set of bit fields containing four fields: num (bits 0-19), count (bits 20-27), btmod (bit 28), and type (bits 29-31).
+ *
+ * Usr.setBitField(bfs.num, n, 1);
+ *
+ * The above set bit field "bfs.num" in numeric variable "n" to the value 1.
+ *
+ * @param {Object} bfs
+ * @return {BitFields}
+ */
+ static defineBitFields(bfs)
+ {
+ var bit = 0;
+ for (var f in bfs) {
+ var width = bfs[f];
+ var mask = ((1 << width) - 1) << bit;
+ bfs[f] = {mask: mask, shift: bit};
+ bit += width;
+ }
+ return bfs;
+ }
+
+ /**
+ * initBitFields(bfs, ...)
+ *
+ * @param {BitFields} bfs
+ * @param {...number} var_args
+ * @return {number} a value containing all supplied bit fields
+ */
+ static initBitFields(bfs, var_args)
+ {
+ var v = 0, i = 1;
+ for (var f in bfs) {
+ if (i >= arguments.length) break;
+ v = Usr.setBitField(bfs[f], v, arguments[i++]);
+ }
+ return v;
+ }
+
+ /**
+ * getBitField(bf, v)
+ *
+ * @param {BitField} bf
+ * @param {number} v is a value containing bit fields
+ * @return {number} the value of the bit field in v defined by bf
+ */
+ static getBitField(bf, v)
+ {
+ return (v & bf.mask) >> bf.shift;
+ }
+
+ /**
+ * setBitField(bf, v, n)
+ *
+ * @param {BitField} bf
+ * @param {number} v is a value containing bit fields
+ * @param {number} n is a value to store in v in the bit field defined by bf
+ * @return {number} updated v
+ */
+ static setBitField(bf, v, n)
+ {
+ return (v & ~bf.mask) | ((n << bf.shift) & bf.mask);
+ }
+
+ /**
+ * indexOf(a, t, i)
+ *
+ * Use this instead of Array.prototype.indexOf() if you can't be sure the browser supports it.
+ *
+ * @param {Array} a
+ * @param {*} t
+ * @param {number} [i]
+ * @returns {number}
+ */
+ static indexOf(a, t, i)
+ {
+ if (Array.prototype.indexOf) {
+ return a.indexOf(t, i);
+ }
+ i = i || 0;
+ if (i < 0) i += a.length;
+ if (i < 0) i = 0;
+ for (var n = a.length; i < n; i++) {
+ if (i in a && a[i] === t) return i;
+ }
+ return -1;
+ }
+}
+
+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];
+
+/**
+ * getTime()
+ *
+ * @return {number} the current time, in milliseconds
+ */
+Usr.getTime = Date.now || function() { return +new Date(); };
+
+if (NODE) module.exports = Usr;
diff --git a/modules/shared/lib/weblib.js b/modules/shared/lib/weblib.js
index 8da5967f7..54be9e08d 100644
--- a/modules/shared/lib/weblib.js
+++ b/modules/shared/lib/weblib.js
@@ -26,6 +26,13 @@
* as to their contents.
*/
+"use strict";
+
+if (NODE) {
+ var Component = require("../../shared/lib/component");
+ var ReportAPI = require("../../shared/lib/reportapi");
+}
+
/*
* According to http://www.w3schools.com/jsref/jsref_obj_global.asp, these are the *global* properties
* and functions of JavaScript-in-the-Browser:
@@ -110,409 +117,828 @@
* stop() Stops the window from loading
*/
-"use strict";
-
-/* global window: true, setTimeout: false, clearTimeout: false, SITEHOST: false */
-
-if (NODE) {
- var Component;
- require("./defines");
- var str = require("./strlib");
- var ReportAPI = require("./reportapi");
-}
-
-var web = {};
-
-/*
- * 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 I/O functions. The simplest solution is to create wrapper functions.
- */
-
-/**
- * log(s, type)
- *
- * For diagnostic output only. DEBUG must be true (or "--debug" specified via the command-line)
- * for Component.log() to display anything.
- *
- * @param {string} [s] is the message text
- * @param {string} [type] is the message type
- */
-web.log = function(s, type)
-{
- if (NODE) {
- if (!Component) Component = require("./component");
+class Web {
+ /**
+ * log(s, type)
+ *
+ * For diagnostic output only. DEBUG must be true (or "--debug" specified via the command-line)
+ * for Component.log() to display anything.
+ *
+ * @param {string} [s] is the message text
+ * @param {string} [type] is the message type
+ */
+ static log(s, type)
+ {
+ Component.log(s, type);
}
- Component.log(s, type);
-};
-/**
- * notice(s, fPrintOnly, id)
- *
- * If Component.notice() calls web.alertUser(), it will fall back to web.log() if all else fails.
- *
- * @param {string} s is the message text
- * @param {boolean} [fPrintOnly]
- * @param {string} [id] is the caller's ID, if any
- */
-web.notice = function(s, fPrintOnly, id)
-{
- if (NODE) {
- if (!Component) Component = require("./component");
+ /**
+ * notice(s, fPrintOnly, id)
+ *
+ * @param {string} s is the message text
+ * @param {boolean} [fPrintOnly]
+ * @param {string} [id] is the caller's ID, if any
+ */
+ static notice(s, fPrintOnly, id)
+ {
+ Component.notice(s, fPrintOnly, id);
}
- Component.notice(s, fPrintOnly, id);
-};
-/**
- * getResource(sURL, dataPost, fAsync, done)
- *
- * Request the specified resource (sURL), and once the request is complete, notify done().
- *
- * Also, if dataPost is set to a string, that string can be used to control the response format;
- * by default, the response format is plain text, but you can specify "bytes" to request arbitrary
- * binary data, which should come back as a string of bytes.
- *
- * TODO: The "bytes" option works by calling overrideMimeType(), which was never a best practice.
- * Instead, we should implement supported response types ("text" and "arraybuffer", at a minimum)
- * by setting xmlHTTP.responseType to one of those values before calling xmlHTTP.send().
- *
- * @param {string} sURL
- * @param {string|Object|null} [dataPost] for a POST request (default is a GET request)
- * @param {boolean} [fAsync] is true for an asynchronous request
- * @param {function(string,string,number)} [done]
- * @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
- */
-web.getResource = function(sURL, dataPost, fAsync, done)
-{
- var nErrorCode = 0, sResource = null, response = null;
+ /**
+ * getResource(sURL, dataPost, fAsync, done)
+ *
+ * Request the specified resource (sURL), and once the request is complete, notify done().
+ *
+ * Also, if dataPost is set to a string, that string can be used to control the response format;
+ * by default, the response format is plain text, but you can specify "bytes" to request arbitrary
+ * binary data, which should come back as a string of bytes.
+ *
+ * TODO: The "bytes" option works by calling overrideMimeType(), which was never a best practice.
+ * Instead, we should implement supported response types ("text" and "arraybuffer", at a minimum)
+ * by setting xmlHTTP.responseType to one of those values before calling xmlHTTP.send().
+ *
+ * ES6 ALERT: Default parameters.
+ *
+ * @param {string} sURL
+ * @param {string|Object|null} [dataPost] for a POST request (default is a GET request)
+ * @param {boolean} [fAsync] is true for an asynchronous request
+ * @param {function(string,string,number)} [done]
+ * @return {Array|null} Array containing [sResource, nErrorCode], or null if no response yet
+ */
+ static getResource(sURL, dataPost, fAsync = false, done)
+ {
+ var nErrorCode = 0, sResource = null, response = null;
- if (typeof resources == 'object' && (sResource = resources[sURL])) {
- if (done) done(sURL, sResource, nErrorCode);
- return [sResource, nErrorCode];
- }
- else if (fAsync && typeof resources == 'function') {
- resources(sURL, function(sResource, nErrorCode) {
+ if (typeof resources == 'object' && (sResource = resources[sURL])) {
if (done) done(sURL, sResource, nErrorCode);
- });
+ return [sResource, nErrorCode];
+ }
+ else if (fAsync && typeof resources == 'function') {
+ resources(sURL, function(sResource, nErrorCode)
+ {
+ if (done) done(sURL, sResource, nErrorCode);
+ });
+ return response;
+ }
+
+ if (DEBUG) {
+ /*
+ * The larger resources we put on archive.pcjs.org should also be available locally...
+ */
+ sURL = sURL.replace(/^http:\/\/archive.pcjs.org(\/.*)\/([^\/]*)$/, "$1/archive/$2");
+ }
+
+ if (NODE) {
+ /*
+ * 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.getResource(sURL, dataPost, fAsync, done);
+ }
+
+ var xmlHTTP = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP"));
+ if (fAsync) {
+ xmlHTTP.onreadystatechange = function()
+ {
+ if (xmlHTTP.readyState === 4) {
+ /*
+ * The following line was 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;
+ */
+ sResource = xmlHTTP.responseText;
+ /*
+ * The normal "success" case is an HTTP status code of 200, but when testing with files loaded
+ * from the local file system (ie, when using the "file:" protocol), we have to be a bit more "flexible".
+ */
+ if (xmlHTTP.status == 200 || !xmlHTTP.status && sResource.length && Web.getHostProtocol() == "file:") {
+ if (MAXDEBUG) Web.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sResource.length + " bytes");
+ }
+ else {
+ nErrorCode = xmlHTTP.status || -1;
+ Web.log("xmlHTTP.onreadystatechange(" + sURL + "): error code " + nErrorCode);
+ }
+ if (done) done(sURL, sResource, nErrorCode);
+ }
+ };
+ }
+
+ if (dataPost && typeof dataPost == "object") {
+ var sDataPost = "";
+ for (var p in dataPost) {
+ if (!dataPost.hasOwnProperty(p)) continue;
+ if (sDataPost) sDataPost += "&";
+ sDataPost += p + '=' + encodeURIComponent(dataPost[p]);
+ }
+ sDataPost = sDataPost.replace(/%20/g, '+');
+ if (MAXDEBUG) Web.log("Web.getResource(POST " + sURL + "): " + sDataPost.length + " bytes");
+ xmlHTTP.open("POST", sURL, fAsync); // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it)
+ xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
+ xmlHTTP.send(sDataPost);
+ } else {
+ if (MAXDEBUG) Web.log("Web.getResource(GET " + sURL + ")");
+ xmlHTTP.open("GET", sURL, fAsync); // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it)
+ if (dataPost == "bytes") {
+ xmlHTTP.overrideMimeType("text/plain; charset=x-user-defined");
+ }
+ xmlHTTP.send();
+ }
+
+ if (!fAsync) {
+ sResource = xmlHTTP.responseText;
+ if (xmlHTTP.status == 200) {
+ if (MAXDEBUG) Web.log("Web.getResource(" + sURL + "): returned " + sResource.length + " bytes");
+ } else {
+ nErrorCode = xmlHTTP.status || -1;
+ Web.log("Web.getResource(" + sURL + "): error code " + nErrorCode);
+ }
+ if (done) done(sURL, sResource, nErrorCode);
+ response = [sResource, nErrorCode];
+ }
return response;
}
- if (DEBUG) {
- /*
- * The larger resources we put on archive.pcjs.org should also be available locally...
- */
- sURL = sURL.replace(/^http:\/\/archive.pcjs.org(\/.*)\/([^\/]*)$/, "$1/archive/$2");
- }
+ /**
+ * parseMemoryResource(sURL, sData)
+ *
+ * @param {string} sURL
+ * @param {string} sData
+ * @return {Object|null} (resource)
+ */
+ static parseMemoryResource(sURL, sData)
+ {
+ var i;
+ var resource = {
+ aBytes: null,
+ aSymbols: null,
+ addrLoad: null,
+ addrExec: null
+ };
- if (NODE) {
- /*
- * 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.getResource(sURL, dataPost, fAsync, done);
- }
+ if (sData.charAt(0) == "[" || sData.charAt(0) == "{") {
+ try {
+ var a, ib, data;
+
+ if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
+ /*
+ * Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid,
+ * but more recent server configs now display a somewhat friendlier HTML error page. The downside,
+ * however, is that the original error has been buried, and we've received "data" that isn't actually
+ * tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message.
+ */
+ throw new Error(sData);
+ }
- var xmlHTTP = (window.XMLHttpRequest? new window.XMLHttpRequest() : new window.ActiveXObject("Microsoft.XMLHTTP"));
- if (fAsync) {
- xmlHTTP.onreadystatechange = function() {
- if (xmlHTTP.readyState === 4) {
/*
- * The following line was 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.
+ * TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
+ * eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
+ * IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
+ * (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
*
- * xmlHTTP.onreadystatechange = undefined;
+ * However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
+ * is a much easier fix. The only drawback is that we must first quote any unquoted property names
+ * and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
+ * the following RegExp replacements take care of those requirements.
+ *
+ * The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
+ * while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
+ * I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
+ * for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
+ * any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
+ *
+ * ["unrecognized disk path: test.img"]
*/
- sResource = xmlHTTP.responseText;
- /*
- * The normal "success" case is an HTTP status code of 200, but when testing with files loaded
- * from the local file system (ie, when using the "file:" protocol), we have to be a bit more "flexible".
- */
- if (xmlHTTP.status == 200 || !xmlHTTP.status && sResource.length && web.getHostProtocol() == "file:") {
- if (MAXDEBUG) web.log("xmlHTTP.onreadystatechange(" + sURL + "): returned " + sResource.length + " bytes");
+ if (sData.indexOf("0x") < 0 && sData.substr(0, 2) != "[\"") {
+ data = JSON.parse(sData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
+ } else {
+ data = eval("(" + sData + ")");
+ }
+
+ resource.addrLoad = data['load'];
+ resource.addrExec = data['exec'];
+
+ if (a = data['bytes']) {
+ resource.aBytes = a;
+ }
+ else if (a = data['words']) {
+ /*
+ * Convert all words into bytes
+ */
+ resource.aBytes = new Array(a.length * 2);
+ for (i = 0, ib = 0; i < a.length; i++) {
+ resource.aBytes[ib++] = a[i] & 0xff;
+ resource.aBytes[ib++] = (a[i] >> 8) & 0xff;
+ Component.assert(!(a[i] & ~0xffff));
+ }
+ }
+ else if (a = data['data']) {
+ /*
+ * Convert all dwords (longs) into bytes
+ */
+ resource.aBytes = new Array(a.length * 4);
+ for (i = 0, ib = 0; i < a.length; i++) {
+ resource.aBytes[ib++] = a[i] & 0xff;
+ resource.aBytes[ib++] = (a[i] >> 8) & 0xff;
+ resource.aBytes[ib++] = (a[i] >> 16) & 0xff;
+ resource.aBytes[ib++] = (a[i] >> 24) & 0xff;
+ }
}
else {
- nErrorCode = xmlHTTP.status || -1;
- web.log("xmlHTTP.onreadystatechange(" + sURL + "): error code " + nErrorCode);
+ resource.aBytes = data;
}
- if (done) done(sURL, sResource, nErrorCode);
+
+ resource.aSymbols = data['symbols'];
+
+ if (!resource.aBytes.length) {
+ Component.error("Empty resource: " + sURL);
+ resource = null;
+ }
+ else if (resource.aBytes.length == 1) {
+ Component.error(resource.aBytes[0]);
+ resource = null;
+ }
+ } catch (e) {
+ Component.error("Resource data error (" + sURL + "): " + e.message);
+ resource = null;
}
+ }
+ else {
+ /*
+ * Parse the data manually; we assume it's a series of hex byte-values separated by whitespace.
+ */
+ var ab = [];
+ var sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, "");
+ var asHexData = sHexData.split(" ");
+ for (i = 0; i < asHexData.length; i++) {
+ var n = parseInt(asHexData[i], 16);
+ if (isNaN(n)) {
+ Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")");
+ break;
+ }
+ ab.push(n & 0xff);
+ }
+ if (i == asHexData.length) resource.aBytes = ab;
+ }
+ return resource;
+ }
+
+ /**
+ * 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, "/devices/pc/machine/5150/mda/64kb/machine.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)
+ */
+ static sendReport(sApp, sVer, sURL, sUser, sType, sReport, sHostName)
+ {
+ var dataPost = {};
+ dataPost[ReportAPI.QUERY.APP] = sApp;
+ dataPost[ReportAPI.QUERY.VER] = sVer;
+ dataPost[ReportAPI.QUERY.URL] = sURL;
+ dataPost[ReportAPI.QUERY.USER] = sUser;
+ dataPost[ReportAPI.QUERY.TYPE] = sType;
+ dataPost[ReportAPI.QUERY.DATA] = sReport;
+ var sReportURL = (sHostName? sHostName : "http://" + SITEHOST) + ReportAPI.ENDPOINT;
+ Web.getResource(sReportURL, dataPost, true);
+ }
+
+ /**
+ * getHost()
+ *
+ * @return {string}
+ */
+ static getHost()
+ {
+ return ("http://" + (window? window.location.host : SITEHOST));
+ }
+
+ /**
+ * getHostURL()
+ *
+ * @return {string|null}
+ */
+ static getHostURL()
+ {
+ return (window? window.location.href : null);
+ }
+
+ /**
+ * getHostProtocol()
+ *
+ * @return {string}
+ */
+ static getHostProtocol()
+ {
+ return (window? window.location.protocol : "file:");
+ }
+
+ /**
+ * getUserAgent()
+ *
+ * @return {string}
+ */
+ static getUserAgent()
+ {
+ return (window? window.navigator.userAgent : "");
+ }
+
+ /**
+ * hasLocalStorage
+ *
+ * true if localStorage support exists, is enabled, and works; false otherwise
+ *
+ * @return {boolean}
+ */
+ static hasLocalStorage()
+ {
+ if (Web.fLocalStorage == null) {
+ var f = false;
+ if (window) {
+ try {
+ window.localStorage.setItem(Web.sLocalStorageTest, Web.sLocalStorageTest);
+ f = (window.localStorage.getItem(Web.sLocalStorageTest) == Web.sLocalStorageTest);
+ window.localStorage.removeItem(Web.sLocalStorageTest);
+ } catch (e) {
+ Web.logLocalStorageError(e);
+ f = false;
+ }
+ }
+ Web.fLocalStorage = f;
+ }
+ return Web.fLocalStorage;
+ }
+
+ /**
+ * logLocalStorageError(e)
+ *
+ * @param {Error} e is an exception
+ */
+ static logLocalStorageError(e)
+ {
+ Web.log(e.message, "localStorage error");
+ }
+
+ /**
+ * 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
+ */
+ static getLocalStorageItem(sKey)
+ {
+ var sValue;
+ if (window) {
+ try {
+ sValue = window.localStorage.getItem(sKey);
+ } catch (e) {
+ Web.logLocalStorageError(e);
+ }
+ }
+ return sValue;
+ }
+
+ /**
+ * setLocalStorageItem(sKey, sValue)
+ *
+ * @param {string} sKey
+ * @param {string} sValue
+ * @return {boolean} true if localStorage is available, false if not
+ */
+ static setLocalStorageItem(sKey, sValue)
+ {
+ try {
+ window.localStorage.setItem(sKey, sValue);
+ return true;
+ } catch (e) {
+ Web.logLocalStorageError(e);
+ }
+ return false;
+ }
+
+ /**
+ * removeLocalStorageItem(sKey)
+ *
+ * @param {string} sKey
+ */
+ static removeLocalStorageItem(sKey)
+ {
+ try {
+ window.localStorage.removeItem(sKey);
+ } catch (e) {
+ Web.logLocalStorageError(e);
+ }
+ }
+
+ /**
+ * getLocalStorageKeys()
+ *
+ * @return {Array}
+ */
+ static getLocalStorageKeys()
+ {
+ var a = [];
+ try {
+ for (var i = 0, c = window.localStorage.length; i < c; i++) {
+ a.push(window.localStorage.key(i));
+ }
+ } catch (e) {
+ Web.logLocalStorageError(e);
+ }
+ return a;
+ }
+
+ /**
+ * reloadPage()
+ */
+ static reloadPage()
+ {
+ 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
+ */
+ static isUserAgent(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 simple solution for now is to remove the call.
+ *
+ * Web.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);
+ }
+ return false;
+ }
+
+ /**
+ * isMobile()
+ *
+ * Check the browser's user-agent string for the substring "Mobi", as per Mozilla recommendation:
+ *
+ * https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent
+ *
+ * @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not
+ */
+ static isMobile()
+ {
+ return Web.isUserAgent("Mobi");
+ }
+
+ /**
+ * getURLParm(sParm)
+ *
+ * @param {string} sParm
+ * @return {string|undefined}
+ */
+ static getURLParm(sParm)
+ {
+ if (!Web.parmsURL) {
+ Web.parmsURL = Web.parseURLParms();
+ }
+ return Web.parmsURL[sParm];
+ }
+
+ /**
+ * parseURLParms(sParms)
+ *
+ * @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?')
+ * @return {Object} containing properties for each parameter found
+ */
+ static parseURLParms(sParms)
+ {
+ var aParms = {};
+ if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") was used
+ 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;
+ }
+
+ /**
+ * downloadFile(sData, sType, fBase64, sFileName)
+ *
+ * @param {string} sData
+ * @param {string} sType
+ * @param {boolean} [fBase64]
+ * @param {string} [sFileName]
+ */
+ static downloadFile(sData, sType, fBase64, sFileName)
+ {
+ var link = null, sAlert;
+ var sURI = "data:application/" + sType + (fBase64? ";base64" : "") + ",";
+
+ if (!Web.isUserAgent("Firefox")) {
+ sURI += (fBase64? sData : encodeURI(sData));
+ } else {
+ sURI += (fBase64? sData : encodeURIComponent(sData));
+ }
+ if (sFileName) {
+ link = document.createElement('a');
+ if (typeof link.download != 'string') link = null;
+ }
+ if (link) {
+ link.href = sURI;
+ link.download = sFileName;
+ document.body.appendChild(link); // Firefox allegedly requires the link to be in the body
+ link.click();
+ document.body.removeChild(link);
+ sAlert = 'Check your Downloads folder for ' + sFileName + '.';
+ } else {
+ window.open(sURI);
+ sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.';
+ }
+ return sAlert;
+ }
+
+ /**
+ * onCountRepeat(n, fnRepeat, fnComplete, msDelay)
+ *
+ * Call fnRepeat() n times with an msDelay millisecond delay between calls,
+ * then call fnComplete() when n has been exhausted OR fnRepeat() returns false.
+ *
+ * @param {number} n
+ * @param {function()} fnRepeat
+ * @param {function()} fnComplete
+ * @param {number} [msDelay]
+ */
+ static onCountRepeat(n, fnRepeat, fnComplete, msDelay)
+ {
+ var fnTimeout = function doCountRepeat()
+ {
+ n -= 1;
+ if (n >= 0) {
+ if (!fnRepeat()) n = 0;
+ }
+ if (n > 0) {
+ setTimeout(fnTimeout, msDelay || 0);
+ return;
+ }
+ fnComplete();
+ };
+ fnTimeout();
+ }
+
+ /**
+ * 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
+ */
+ static onClickRepeat(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()
+ {
+ // Web.log("onMouseDown()");
+ if (!fIgnoreMouseEvents) {
+ if (!timer) {
+ ms = msDelay;
+ fnRepeat();
+ }
+ }
+ };
+ e.ontouchstart = function()
+ {
+ // Web.log("onTouchStart()");
+ if (!timer) {
+ ms = msDelay;
+ fnRepeat();
+ }
+ };
+ e.onmouseup = e.onmouseout = function()
+ {
+ // Web.log("onMouseUp()/onMouseOut()");
+ if (timer) {
+ clearTimeout(timer);
+ timer = null;
+ }
+ };
+ e.ontouchend = e.ontouchcancel = function()
+ {
+ // Web.log("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;
};
}
- if (dataPost && typeof dataPost == "object") {
- var sDataPost = "";
- for (var p in dataPost) {
- if (!dataPost.hasOwnProperty(p)) continue;
- if (sDataPost) sDataPost += "&";
- sDataPost += p + '=' + encodeURIComponent(dataPost[p]);
+ /**
+ * onPageEvent(sName, fn)
+ *
+ * For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but
+ * instead use Web.onInit(), Web.onShow(), and Web.onExit(), respectively.
+ *
+ * The only components that should still use onPageEvent() are THIS component (see the bottom of this file)
+ * and components that need to capture other events (eg, the 'onresize' event in the Video component).
+ *
+ * This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers
+ * for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'],
+ * etc, themselves. However, that's less of a concern now, because assuming everyone else is now using
+ * onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one.
+ *
+ * NOTE: 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).
+ *
+ * @param {string} sFunc
+ * @param {function()} fn
+ */
+ static onPageEvent(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();
+ };
+ }
}
- sDataPost = sDataPost.replace(/%20/g, '+');
- if (MAXDEBUG) web.log("web.getResource(POST " + sURL + "): " + sDataPost.length + " bytes");
- xmlHTTP.open("POST", sURL, !!fAsync); // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it)
- xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
- xmlHTTP.send(sDataPost);
- } else {
- if (MAXDEBUG) web.log("web.getResource(GET " + sURL + ")");
- xmlHTTP.open("GET", sURL, !!fAsync); // ensure that fAsync is a valid boolean (Internet Explorer xmlHTTP functions insist on it)
- if (dataPost == "bytes") {
- xmlHTTP.overrideMimeType("text/plain; charset=x-user-defined");
- }
- xmlHTTP.send();
- }
-
- if (!fAsync) {
- sResource = xmlHTTP.responseText;
- if (xmlHTTP.status == 200) {
- if (MAXDEBUG) web.log("web.getResource(" + sURL + "): returned " + sResource.length + " bytes");
- } else {
- nErrorCode = xmlHTTP.status || -1;
- web.log("web.getResource(" + sURL + "): error code " + nErrorCode);
- }
- if (done) done(sURL, sResource, nErrorCode);
- response = [sResource, nErrorCode];
- }
- return response;
-};
-
-/**
- * parseMemoryResource(sURL, sData)
- *
- * @param {string} sURL
- * @param {string} sData
- * @return {Object|null} (resource)
- */
-web.parseMemoryResource = function(sURL, sData)
-{
- var i;
- var resource = {
- aBytes: null,
- aSymbols: null,
- addrLoad: null,
- addrExec: null
};
- if (sData.charAt(0) == "[" || sData.charAt(0) == "{") {
- try {
- var a, ib, data;
+ /**
+ * onInit(fn)
+ *
+ * Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler.
+ *
+ * @param {function()} fn
+ */
+ static onInit(fn)
+ {
+ Web.aPageEventHandlers['init'].push(fn);
+ };
- if (sData.substr(0, 1) == "<") { // if the "data" begins with a "<"...
- /*
- * Early server configs reported an error (via the nErrorCode parameter) if a tape URL was invalid,
- * but more recent server configs now display a somewhat friendlier HTML error page. The downside,
- * however, is that the original error has been buried, and we've received "data" that isn't actually
- * tape data. So if the data we've received appears to be "HTML-like", we treat it as an error message.
- */
- throw new Error(sData);
- }
+ /**
+ * onShow(fn)
+ *
+ * @param {function()} fn
+ *
+ * Use this instead of setting window.onpageshow. Allows multiple JavaScript modules to define their own 'onpageshow' event handler.
+ */
+ static onShow(fn)
+ {
+ Web.aPageEventHandlers['show'].push(fn);
+ };
- /*
- * TODO: IE9 is rather unfriendly and restrictive with regard to how much data it's willing to
- * eval(). In particular, the 10Mb disk image we use for the Windows 1.01 demo config fails in
- * IE9 with an "Out of memory" exception. One work-around would be to chop the data into chunks
- * (perhaps one track per chunk, using regular expressions) and then manually re-assemble it.
- *
- * However, it turns out that using JSON.parse(sDiskData) instead of eval("(" + sDiskData + ")")
- * is a much easier fix. The only drawback is that we must first quote any unquoted property names
- * and remove any comments, because while eval() was cool with them, JSON.parse() is more particular;
- * the following RegExp replacements take care of those requirements.
- *
- * The use of hex values is something else that eval() was OK with, but JSON.parse() is not, and
- * while I've stopped using hex values in DumpAPI responses (at least when "format=json" is specified),
- * I can't guarantee they won't show up in "legacy" images, and there's no simple RegExp replacement
- * for transforming hex values into decimal values, so I cop out and fall back to eval() if I detect
- * any hex prefixes ("0x") in the sequence. Ditto for error messages, which appear like so:
- *
- * ["unrecognized disk path: test.img"]
- */
- if (sData.indexOf("0x") < 0 && sData.substr(0, 2) != "[\"") {
- data = JSON.parse(sData.replace(/([a-z]+):/gm, "\"$1\":").replace(/\/\/[^\n]*/gm, ""));
- } else {
- data = eval("(" + sData + ")");
- }
+ /**
+ * onExit(fn)
+ *
+ * @param {function()} fn
+ *
+ * Use this instead of setting window.onunload. Allows multiple JavaScript modules to define their own 'onunload' event handler.
+ */
+ static onExit(fn)
+ {
+ Web.aPageEventHandlers['exit'].push(fn);
+ };
- resource.addrLoad = data['load'];
- resource.addrExec = data['exec'];
-
- if (a = data['bytes']) {
- resource.aBytes = a;
- }
- else if (a = data['words']) {
- /*
- * Convert all words into bytes
- */
- resource.aBytes = new Array(a.length * 2);
- for (i = 0, ib = 0; i < a.length; i++) {
- resource.aBytes[ib++] = a[i] & 0xff;
- resource.aBytes[ib++] = (a[i] >> 8) & 0xff;
- Component.assert(!(a[i] & ~0xffff));
+ /**
+ * doPageEvent(afn)
+ *
+ * @param {Array.} afn
+ */
+ static doPageEvent(afn)
+ {
+ if (Web.fPageEventsEnabled) {
+ try {
+ for (var i = 0; i < afn.length; i++) {
+ afn[i]();
}
+ } catch (e) {
+ Web.notice("An unexpected exception occurred:\n\n" + e.message + "\n\nPlease send this information to support@pcjs.org. Thanks.");
}
- else if (a = data['data']) {
- /*
- * Convert all dwords (longs) into bytes
- */
- resource.aBytes = new Array(a.length * 4);
- for (i = 0, ib = 0; i < a.length; i++) {
- resource.aBytes[ib++] = a[i] & 0xff;
- resource.aBytes[ib++] = (a[i] >> 8) & 0xff;
- resource.aBytes[ib++] = (a[i] >> 16) & 0xff;
- resource.aBytes[ib++] = (a[i] >> 24) & 0xff;
- }
- }
- else {
- resource.aBytes = data;
- }
+ }
+ };
- resource.aSymbols = data['symbols'];
+ /**
+ * enablePageEvents(fEnable)
+ *
+ * @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default)
+ */
+ static enablePageEvents(fEnable)
+ {
+ if (!Web.fPageEventsEnabled && fEnable) {
+ Web.fPageEventsEnabled = true;
+ if (Web.fPageLoaded) Web.sendPageEvent('init');
+ if (Web.fPageShowed) Web.sendPageEvent('show');
+ return;
+ }
+ Web.fPageEventsEnabled = fEnable;
+ }
- if (!resource.aBytes.length) {
- Component.error("Empty resource: " + sURL);
- resource = null;
- }
- else if (resource.aBytes.length == 1) {
- Component.error(resource.aBytes[0]);
- resource = null;
- }
- } catch (e) {
- Component.error("Resource data error (" + sURL + "): " + e.message);
- resource = null;
+ /**
+ * sendPageEvent(sEvent)
+ *
+ * This allows us to manually trigger page events.
+ *
+ * @param {string} sEvent (one of 'init', 'show' or 'exit')
+ */
+ static sendPageEvent(sEvent)
+ {
+ if (Web.aPageEventHandlers[sEvent]) {
+ Web.doPageEvent(Web.aPageEventHandlers[sEvent]);
}
}
- else {
- /*
- * Parse the data manually; we assume it's a series of hex byte-values separated by whitespace.
- */
- var ab = [];
- var sHexData = sData.replace(/\n/gm, " ").replace(/ +$/, "");
- var asHexData = sHexData.split(" ");
- for (i = 0; i < asHexData.length; i++) {
- var n = parseInt(asHexData[i], 16);
- if (isNaN(n)) {
- Component.error("Resource data error (" + sURL + "): invalid hex byte (" + asHexData[i] + ")");
- break;
- }
- ab.push(n & 0xff);
- }
- if (i == asHexData.length) resource.aBytes = ab;
- }
- return resource;
+}
+
+Web.parmsURL = null; // initialized on first call to parseURLParms()
+
+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)
};
-/**
- * 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, "/devices/pc/machine/5150/mda/64kb/machine.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 dataPost = {};
- dataPost[ReportAPI.QUERY.APP] = sApp;
- dataPost[ReportAPI.QUERY.VER] = sVer;
- dataPost[ReportAPI.QUERY.URL] = sURL;
- dataPost[ReportAPI.QUERY.USER] = sUser;
- dataPost[ReportAPI.QUERY.TYPE] = sType;
- dataPost[ReportAPI.QUERY.DATA] = sReport;
- var sReportURL = (sHostName? sHostName : "http://" + SITEHOST) + ReportAPI.ENDPOINT;
- web.getResource(sReportURL, dataPost, true);
-};
-
-/**
- * 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);
-};
-
-/**
- * getHostProtocol()
- *
- * @return {string}
- */
-web.getHostProtocol = function()
-{
- return (window? window.location.protocol : "file:");
-};
-
-/**
- * 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 web.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;
-};
+Web.fPageLoaded = false; // set once the page's first 'onload' event has occurred
+Web.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred
+Web.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents()
/**
* fLocalStorage
@@ -521,501 +947,27 @@ web.promptUser = function(sPrompt, sDefault)
*
* @type {boolean|null}
*/
-web.fLocalStorage = null;
+Web.fLocalStorage = null;
/**
- * TODO: Is there any way to get the Closure Compiler to stop inlining this string? This
- * isn't cutting it.
+ * TODO: Is there any way to get the Closure Compiler to stop inlining this string? This isn't cutting it.
*
* @const {string}
*/
-web.sLocalStorageTest = "PCjs.localStorage";
+Web.sLocalStorageTest = "PCjs.localStorage";
-/**
- * hasLocalStorage
- *
- * true if localStorage support exists, is enabled, and works; false otherwise
- *
- * @return {boolean}
- */
-web.hasLocalStorage = function() {
- if (web.fLocalStorage == null) {
- var f = false;
- if (window) {
- try {
- window.localStorage.setItem(web.sLocalStorageTest, web.sLocalStorageTest);
- f = (window.localStorage.getItem(web.sLocalStorageTest) == web.sLocalStorageTest);
- window.localStorage.removeItem(web.sLocalStorageTest);
- } catch(e) {
- web.logLocalStorageError(e);
- f = false;
- }
- }
- web.fLocalStorage = f;
- }
- return web.fLocalStorage;
-};
-
-/**
- * logLocalStorageError(e)
- *
- * @param {Error} e is an exception
- */
-web.logLocalStorageError = function(e)
-{
- web.log(e.message, "localStorage error");
-};
-
-/**
- * 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) {
- try {
- sValue = window.localStorage.getItem(sKey);
- } catch(e) {
- web.logLocalStorageError(e);
- }
- }
- 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)
-{
- try {
- window.localStorage.setItem(sKey, sValue);
- return true;
- } catch(e) {
- web.logLocalStorageError(e);
- }
- return false;
-};
-
-/**
- * removeLocalStorageItem(sKey)
- *
- * @param {string} sKey
- */
-web.removeLocalStorageItem = function(sKey)
-{
- try {
- window.localStorage.removeItem(sKey);
- } catch(e) {
- web.logLocalStorageError(e);
- }
-};
-
-/**
- * getLocalStorageKeys()
- *
- * @return {Array}
- */
-web.getLocalStorageKeys = function()
-{
- var a = [];
- try {
- for (var i = 0, c = window.localStorage.length; i < c; i++) {
- a.push(window.localStorage.key(i));
- }
- } catch(e) {
- web.logLocalStorageError(e);
- }
- return a;
-};
-
-/**
- * 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 simple solution for now is to remove the call.
- *
- * web.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;
-};
-
-/**
- * isMobile()
- *
- * Check the browser's user-agent string for the substring "Mobi", as per Mozilla recommendation:
- *
- * https://developer.mozilla.org/en-US/docs/Browser_detection_using_the_user_agent
- *
- * @return {boolean} is true if the browser appears to be a mobile (ie, non-desktop) web browser, false if not
- */
-web.isMobile = function()
-{
- return web.isUserAgent("Mobi");
-};
-
-/**
- * getURLParm(sParm)
- *
- * @param {string} sParm
- * @return {string|undefined}
- */
-web.getURLParm = function(sParm)
-{
- if (!web.parmsURL) {
- web.parmsURL = web.parseURLParms();
- }
- return web.parmsURL[sParm];
-};
-
-/**
- * parseURLParms(sParms)
- *
- * @param {string} [sParms] containing the parameter portion of a URL (ie, after the '?')
- * @return {Object} containing properties for each parameter found
- */
-web.parseURLParms = function(sParms)
-{
- var aParms = {};
- if (window) { // an alternative to "if (typeof module === 'undefined')" if require("defines") was used
- 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;
-};
-
-/**
- * downloadFile(sData, sType, fBase64, sFileName)
- *
- * @param {string} sData
- * @param {string} sType
- * @param {boolean} [fBase64]
- * @param {string} [sFileName]
- */
-web.downloadFile = function(sData, sType, fBase64, sFileName)
-{
- var link = null, sAlert;
- var sURI = "data:application/" + sType + (fBase64? ";base64" : "") + ",";
-
- if (!web.isUserAgent("Firefox")) {
- sURI += (fBase64? sData : encodeURI(sData));
- } else {
- sURI += (fBase64? sData : encodeURIComponent(sData));
- }
- if (sFileName) {
- link = document.createElement('a');
- if (typeof link.download != 'string') link = null;
- }
- if (link) {
- link.href = sURI;
- link.download = sFileName;
- document.body.appendChild(link); // Firefox allegedly requires the link to be in the body
- link.click();
- document.body.removeChild(link);
- sAlert = 'Check your Downloads folder for ' + sFileName + '.';
- } else {
- window.open(sURI);
- sAlert = 'Check your browser for a new window/tab containing the requested data' + (sFileName? (' (' + sFileName + ')') : '') + '.';
- }
- return sAlert;
-};
-
-/**
- * onCountRepeat(n, fnRepeat, fnComplete, msDelay)
- *
- * Call fnRepeat() n times with an msDelay millisecond delay between calls,
- * then call fnComplete() when n has been exhausted OR fnRepeat() returns false.
- *
- * @param {number} n
- * @param {function()} fnRepeat
- * @param {function()} fnComplete
- * @param {number} [msDelay]
- */
-web.onCountRepeat = function(n, fnRepeat, fnComplete, msDelay)
-{
- var fnTimeout = function doCountRepeat() {
- n -= 1;
- if (n >= 0) {
- if (!fnRepeat()) n = 0;
- }
- if (n > 0) {
- setTimeout(fnTimeout, msDelay || 0);
- return;
- }
- fnComplete();
- };
- fnTimeout();
-};
-
-/**
- * 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() {
- // web.log("onMouseDown()");
- if (!fIgnoreMouseEvents) {
- if (!timer) {
- ms = msDelay;
- fnRepeat();
- }
- }
- };
- e.ontouchstart = function() {
- // web.log("onTouchStart()");
- if (!timer) {
- ms = msDelay;
- fnRepeat();
- }
- };
- e.onmouseup = e.onmouseout = function() {
- // web.log("onMouseUp()/onMouseOut()");
- if (timer) {
- clearTimeout(timer);
- timer = null;
- }
- };
- e.ontouchend = e.ontouchcancel = function() {
- // web.log("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.parmsURL = null; // initialized on first call to parseURLParms()
-
-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.fPageLoaded = false; // set once the page's first 'onload' event has occurred
-web.fPageShowed = false; // set once the page's first 'onpageshow' event has occurred
-web.fPageEventsEnabled = true; // default is true, set to false (or true) by enablePageEvents()
-
-/**
- * onPageEvent(sName, fn)
- *
- * For 'onload', 'onunload', and 'onpageshow' events, most callers should NOT use this function, but
- * instead use web.onInit(), web.onShow(), and web.onExit(), respectively.
- *
- * The only components that should still use onPageEvent() are THIS component (see the bottom of this file)
- * and components that need to capture other events (eg, the 'onresize' event in the Video component).
- *
- * This function creates a chain of callbacks, allowing multiple JavaScript modules to define handlers
- * for the same event, which wouldn't be possible if everyone modified window['onload'], window['onunload'],
- * etc, themselves. However, that's less of a concern now, because assuming everyone else is now using
- * onInit(), onExit(), etc, then there really IS only one component setting the window callback: this one.
- *
- * NOTE: 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).
- *
- * @param {string} sFunc
- * @param {function()} fn
- */
-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)
- *
- * Use this instead of setting window.onload. Allows multiple JavaScript modules to define their own 'onload' event handler.
- *
- * @param {function()} fn
- */
-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.} afn
- */
-web.doPageEvent = function(afn)
-{
- if (web.fPageEventsEnabled) {
- try {
- for (var i = 0; i < afn.length; i++) {
- afn[i]();
- }
- } catch(e) {
- web.notice("An unexpected exception occurred:\n\n" + e.message + "\n\nPlease send this information to support@pcjs.org. Thanks.");
- }
- }
-};
-
-/**
- * enablePageEvents(fEnable)
- *
- * @param {boolean} fEnable is true to enable page events, false to disable (they're enabled by default)
- */
-web.enablePageEvents = function(fEnable)
-{
- if (!web.fPageEventsEnabled && fEnable) {
- web.fPageEventsEnabled = true;
- if (web.fPageLoaded) web.sendPageEvent('init');
- if (web.fPageShowed) web.sendPageEvent('show');
- return;
- }
- 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.fPageLoaded = true;
- web.doPageEvent(web.aPageEventHandlers['init']);
+Web.onPageEvent('onload', function onPageLoad() {
+ Web.fPageLoaded = true;
+ Web.doPageEvent(Web.aPageEventHandlers['init']);
});
-web.onPageEvent('onpageshow', function onPageShow() {
- web.fPageShowed = true;
- web.doPageEvent(web.aPageEventHandlers['show']);
+Web.onPageEvent('onpageshow', function onPageShow() {
+ Web.fPageShowed = true;
+ Web.doPageEvent(Web.aPageEventHandlers['show']);
});
-web.onPageEvent(web.isUserAgent("Opera") || web.isUserAgent("iOS")? 'onunload' : 'onbeforeunload', function onPageUnload() {
- web.doPageEvent(web.aPageEventHandlers['exit']);
+Web.onPageEvent(Web.isUserAgent("Opera") || Web.isUserAgent("iOS")? 'onunload' : 'onbeforeunload', function onPageUnload() {
+ Web.doPageEvent(Web.aPageEventHandlers['exit']);
});
-if (NODE) module.exports = web;
+if (NODE) module.exports = Web;
diff --git a/package.json b/package.json
index ba0e5a8bb..a8e91475a 100644
--- a/package.json
+++ b/package.json
@@ -75,13 +75,13 @@
"./modules/c1pjs/templates/components.css"
],
"c1pJSFiles": [
- "./modules/shared/es6/defines.js",
- "./modules/shared/es6/dumpapi.js",
- "./modules/shared/es6/reportapi.js",
- "./modules/shared/es6/strlib.js",
- "./modules/shared/es6/usrlib.js",
- "./modules/shared/es6/weblib.js",
- "./modules/shared/es6/component.js",
+ "./modules/shared/lib/defines.js",
+ "./modules/shared/lib/dumpapi.js",
+ "./modules/shared/lib/reportapi.js",
+ "./modules/shared/lib/strlib.js",
+ "./modules/shared/lib/usrlib.js",
+ "./modules/shared/lib/weblib.js",
+ "./modules/shared/lib/component.js",
"./modules/c1pjs/lib/defines.js",
"./modules/c1pjs/lib/panel.js",
"./modules/c1pjs/lib/cpu.js",
@@ -93,22 +93,22 @@
"./modules/c1pjs/lib/disk.js",
"./modules/c1pjs/lib/debugger.js",
"./modules/c1pjs/lib/computer.js",
- "./modules/shared/es6/embed.js"
+ "./modules/shared/lib/embed.js"
],
"pcCSSFiles": [
"./modules/shared/templates/components.css"
],
"pcX86Files": [
- "./modules/shared/es6/defines.js",
- "./modules/shared/es6/diskapi.js",
- "./modules/shared/es6/dumpapi.js",
- "./modules/shared/es6/reportapi.js",
- "./modules/shared/es6/userapi.js",
- "./modules/shared/es6/keys.js",
- "./modules/shared/es6/strlib.js",
- "./modules/shared/es6/usrlib.js",
- "./modules/shared/es6/weblib.js",
- "./modules/shared/es6/component.js",
+ "./modules/shared/lib/defines.js",
+ "./modules/shared/lib/diskapi.js",
+ "./modules/shared/lib/dumpapi.js",
+ "./modules/shared/lib/reportapi.js",
+ "./modules/shared/lib/userapi.js",
+ "./modules/shared/lib/keys.js",
+ "./modules/shared/lib/strlib.js",
+ "./modules/shared/lib/usrlib.js",
+ "./modules/shared/lib/weblib.js",
+ "./modules/shared/lib/component.js",
"./modules/pcx86/lib/defines.js",
"./modules/pcx86/lib/x86.js",
"./modules/pcx86/lib/interrupts.js",
@@ -136,23 +136,23 @@
"./modules/pcx86/lib/disk.js",
"./modules/pcx86/lib/fdc.js",
"./modules/pcx86/lib/hdc.js",
- "./modules/shared/es6/debugger.js",
+ "./modules/shared/lib/debugger.js",
"./modules/pcx86/lib/debugger.js",
"./modules/pcx86/lib/computer.js",
- "./modules/shared/es6/state.js",
- "./modules/shared/es6/embed.js",
- "./modules/shared/es6/save.js"
+ "./modules/shared/lib/state.js",
+ "./modules/shared/lib/embed.js",
+ "./modules/shared/lib/save.js"
],
"pc8080Files": [
- "./modules/shared/es6/defines.js",
- "./modules/shared/es6/dumpapi.js",
- "./modules/shared/es6/reportapi.js",
- "./modules/shared/es6/userapi.js",
- "./modules/shared/es6/keys.js",
- "./modules/shared/es6/component.js",
- "./modules/shared/es6/strlib.js",
- "./modules/shared/es6/usrlib.js",
- "./modules/shared/es6/weblib.js",
+ "./modules/shared/lib/defines.js",
+ "./modules/shared/lib/dumpapi.js",
+ "./modules/shared/lib/reportapi.js",
+ "./modules/shared/lib/userapi.js",
+ "./modules/shared/lib/keys.js",
+ "./modules/shared/lib/component.js",
+ "./modules/shared/lib/strlib.js",
+ "./modules/shared/lib/usrlib.js",
+ "./modules/shared/lib/weblib.js",
"./modules/pc8080/lib/defines.js",
"./modules/pc8080/lib/cpudef.js",
"./modules/pc8080/lib/messages.js",
@@ -168,23 +168,23 @@
"./modules/pc8080/lib/keyboard.js",
"./modules/pc8080/lib/video.js",
"./modules/pc8080/lib/serial.js",
- "./modules/shared/es6/debugger.js",
+ "./modules/shared/lib/debugger.js",
"./modules/pc8080/lib/debugger.js",
"./modules/pc8080/lib/computer.js",
- "./modules/shared/es6/state.js",
- "./modules/shared/es6/embed.js"
+ "./modules/shared/lib/state.js",
+ "./modules/shared/lib/embed.js"
],
"pdp11Files": [
- "./modules/shared/es6/defines.js",
- "./modules/shared/es6/diskapi.js",
- "./modules/shared/es6/dumpapi.js",
- "./modules/shared/es6/reportapi.js",
- "./modules/shared/es6/userapi.js",
- "./modules/shared/es6/keys.js",
- "./modules/shared/es6/component.js",
- "./modules/shared/es6/strlib.js",
- "./modules/shared/es6/usrlib.js",
- "./modules/shared/es6/weblib.js",
+ "./modules/shared/lib/defines.js",
+ "./modules/shared/lib/diskapi.js",
+ "./modules/shared/lib/dumpapi.js",
+ "./modules/shared/lib/reportapi.js",
+ "./modules/shared/lib/userapi.js",
+ "./modules/shared/lib/keys.js",
+ "./modules/shared/lib/component.js",
+ "./modules/shared/lib/strlib.js",
+ "./modules/shared/lib/usrlib.js",
+ "./modules/shared/lib/weblib.js",
"./modules/pdp11/lib/defines.js",
"./modules/pdp11/lib/messages.js",
"./modules/pdp11/lib/panel.js",
@@ -203,10 +203,10 @@
"./modules/pdp11/lib/drive.js",
"./modules/pdp11/lib/rk11.js",
"./modules/pdp11/lib/rl11.js",
- "./modules/shared/es6/debugger.js",
+ "./modules/shared/lib/debugger.js",
"./modules/pdp11/lib/debugger.js",
"./modules/pdp11/lib/computer.js",
- "./modules/shared/es6/state.js",
- "./modules/shared/es6/embed.js"
+ "./modules/shared/lib/state.js",
+ "./modules/shared/lib/embed.js"
]
}
diff --git a/versions/pcx86/1.33.0/pcx86-dbg.js b/versions/pcx86/1.33.0/pcx86-dbg.js
index 8b97c527f..4ccd93d2e 100644
--- a/versions/pcx86/1.33.0/pcx86-dbg.js
+++ b/versions/pcx86/1.33.0/pcx86-dbg.js
@@ -1,20 +1,20 @@
(function(){/*
- http://pcjs.org/modules/shared/es6/diskapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/dumpapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/reportapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/userapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/keys.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/strlib.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/weblib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/diskapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/x86.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/interrupts.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/messages.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/debugger.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/state.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/embed.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/defines.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/usrlib.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/component.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/state.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/embed.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/panel.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/bus.js (C) Jeff Parsons 2012-2017
@@ -39,9 +39,9 @@
http://pcjs.org/modules/pcx86/lib/disk.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/fdc.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/hdc.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/debugger.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/computer.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/save.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/save.js (C) Jeff Parsons 2012-2017
*/
var l,aa;function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a;for(var d in b)if(Object.defineProperties){var e=Object.getOwnPropertyDescriptor(b,d);e&&Object.defineProperty(a,d,e)}else a[d]=b[d]}
var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={jp:0,lp:1,mp:2,hl:3,np:4,op:5,pp:6,qp:7,rp:8,sp:9,tp:10,up:11,vp:12,wp:13,xp:14,yp:15,zp:16,Ap:17,Bp:18,Cp:19,Dp:20,Ep:21,Fp:22,Gp:23,Hp:24,Ip:25,Jp:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39,"(":40,")":41,"*":42,
diff --git a/versions/pcx86/1.33.0/pcx86.js b/versions/pcx86/1.33.0/pcx86.js
index 5169e063c..762375ab7 100644
--- a/versions/pcx86/1.33.0/pcx86.js
+++ b/versions/pcx86/1.33.0/pcx86.js
@@ -1,20 +1,20 @@
(function(){/*
- http://pcjs.org/modules/shared/es6/diskapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/dumpapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/reportapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/userapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/keys.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/strlib.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/weblib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/diskapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/x86.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/interrupts.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/messages.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/debugger.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/state.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/embed.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/defines.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/usrlib.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/component.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/state.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/embed.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/panel.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/bus.js (C) Jeff Parsons 2012-2017
@@ -39,9 +39,9 @@
http://pcjs.org/modules/pcx86/lib/disk.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/fdc.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/hdc.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/debugger.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pcx86/lib/computer.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/save.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/save.js (C) Jeff Parsons 2012-2017
*/
var k,aa;function ba(a,b){function c(){}c.prototype=b.prototype;a.prototype=new c;a.prototype.constructor=a;for(var d in b)if(Object.defineProperties){var e=Object.getOwnPropertyDescriptor(b,d);e&&Object.defineProperty(a,d,e)}else a[d]=b[d]}
var da={163840:[40,1,8,,254],184320:[40,1,9,,252],327680:[40,2,8,,255],368640:[40,2,9,,253],737280:[80,2,9,,249],1228800:[80,2,15,,249],1474560:[80,2,18,,240],2949120:[80,2,36,,240],21368320:[615,4,17],2494464:[203,2,12,512],5242880:[256,2,40,256],10485760:[512,2,40,256]},n={Ln:0,Nn:1,On:2,Zj:3,Pn:4,Qn:5,Rn:6,Sn:7,Tn:8,Un:9,Vn:10,Wn:11,Xn:12,Yn:13,Zn:14,$n:15,ao:16,bo:17,co:18,eo:19,fo:20,ho:21,io:22,jo:23,ko:24,lo:25,mo:26," ":32,"!":33,'"':34,"#":35,$:36,"%":37,"&":38,"'":39,"(":40,")":41,"*":42,
diff --git a/versions/pdpjs/1.33.0/pdp11-dbg.js b/versions/pdpjs/1.33.0/pdp11-dbg.js
index a178e93a0..be2809dee 100644
--- a/versions/pdpjs/1.33.0/pdp11-dbg.js
+++ b/versions/pdpjs/1.33.0/pdp11-dbg.js
@@ -1,11 +1,11 @@
(function(){/*
- http://pcjs.org/modules/shared/es6/diskapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/dumpapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/reportapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/userapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/keys.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/strlib.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/weblib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/diskapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/messages.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/panel.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/device.js (C) Jeff Parsons 2012-2017
@@ -17,11 +17,11 @@
http://pcjs.org/modules/pdp11/lib/rk11.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/rl11.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/computer.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/state.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/embed.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/defines.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/component.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/usrlib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/state.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/embed.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/bus.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/memory.js (C) Jeff Parsons 2012-2017
@@ -30,7 +30,7 @@
http://pcjs.org/modules/pdp11/lib/serial.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/disk.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/drive.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/debugger.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/debugger.js (C) Jeff Parsons 2012-2017
*/
var h,aa="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(c.get||c.set)throw new TypeError("ES3 does not support getters and setters.");a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)},ba="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global?global:this;function ca(){ca=function(){};ba.Symbol||(ba.Symbol=da)}var ea=0;function da(a){return"jscomp_symbol_"+(a||"")+ea++}
diff --git a/versions/pdpjs/1.33.0/pdp11.js b/versions/pdpjs/1.33.0/pdp11.js
index 0c70efffc..1921431b4 100644
--- a/versions/pdpjs/1.33.0/pdp11.js
+++ b/versions/pdpjs/1.33.0/pdp11.js
@@ -1,11 +1,11 @@
(function(){/*
- http://pcjs.org/modules/shared/es6/diskapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/dumpapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/reportapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/userapi.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/keys.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/strlib.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/weblib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/diskapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/dumpapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/reportapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/userapi.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/keys.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/strlib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/weblib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/messages.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/panel.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/device.js (C) Jeff Parsons 2012-2017
@@ -17,11 +17,11 @@
http://pcjs.org/modules/pdp11/lib/rk11.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/rl11.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/computer.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/state.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/embed.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/defines.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/component.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/usrlib.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/state.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/embed.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/defines.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/component.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/usrlib.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/defines.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/bus.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/memory.js (C) Jeff Parsons 2012-2017
@@ -30,7 +30,7 @@
http://pcjs.org/modules/pdp11/lib/serial.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/disk.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/drive.js (C) Jeff Parsons 2012-2017
- http://pcjs.org/modules/shared/es6/debugger.js (C) Jeff Parsons 2012-2017
+ http://pcjs.org/modules/shared/lib/debugger.js (C) Jeff Parsons 2012-2017
http://pcjs.org/modules/pdp11/lib/debugger.js (C) Jeff Parsons 2012-2017
*/
var h,aa="function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,c){if(c.get||c.set)throw new TypeError("ES3 does not support getters and setters.");a!=Array.prototype&&a!=Object.prototype&&(a[b]=c.value)},ba="undefined"!=typeof window&&window===this?this:"undefined"!=typeof global?global:this;function ca(){ca=function(){};ba.Symbol||(ba.Symbol=da)}var ea=0;function da(a){return"jscomp_symbol_"+(a||"")+ea++}