"use strict";
/**********************************************************************
* Common utilities that don't involve use in a webpage.
*
* Most functions should be self-explanatory via their names.
**********************************************************************/
/** Retrieves the global scope
*/
var GLOBAL = (function()
{
return this || (1, eval)('this');
}());
/** Character constants
*/
var NUMBERS = '0123456789';
var LOWERCASE = 'abcdefghijklmnopqrstuvwxyz';
var UPPERCASE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var ALPHA = LOWERCASE + UPPERCASE;
var ALNUM = LOWERCASE + UPPERCASE + NUMBERS;
/** For use in the timezone functions
*/
var TIMEZONES = {};
for (let i = -12; i < 13; i++)
{
TIMEZONES['UTC ' + (i < 0 ? i : '+' + i)] = i;
}
// ====================================================================
/** Returns true if the string contains only the allowed characters.
*
* @param {string} stringtotest
* @param {string} allowedcharacters
*/
function ValidString(stringtotest, allowedcharacters)
{
if (stringtotest == '' || !IsString(stringtotest))
{
return 0;
}
for (let i = 0; i < stringtotest.length; i++)
{
if (allowedcharacters.indexOf(stringtotest.charAt(i), 0) == -1)
{
return 0;
}
}
return 1;
}
// ====================================================================
/** Returns true if the string contains only numbers.
*
* @param {string} stringtotest
*/
function IsNumber(stringtotest) {return ValidString(stringtotest, NUMBERS);}
// ====================================================================
/** Returns true if the string contains only lower case ASCII letters.
*
* @param {string} stringtotest
*/
function IsLower(stringtotest) {return ValidString(stringtotest, LOWERCASE);}
// ====================================================================
/** Returns true if the string contains only upper case ASCII letters.
*
* @param {string} stringtotest
*/
function IsUpper(stringtotest) {return ValidString(stringtotest, UPPERCASE);}
// ====================================================================
/** Returns true if the string contains only ASCII alphabetical characters.
*
* @param {string} stringtotest
*/
function IsAlpha(stringtotest) {return ValidString(stringtotest, ALPHA);}
// ====================================================================
/** Returns true if the string contains only ASCII alphanumeric characters.
*
* @param {string} stringtotest
*/
function IsAlphaNum(stringtotest) {return ValidString(stringtotest, ALNUM);}
// ====================================================================
/** Returns true if the string appears to be a valid email address.
*
* Thanks to https://codeforgeek.com/how-to-validate-email-address-javascript
*
* @param {string} stringtotest
*/
function IsEmail(stringtotest)
{
var expression = /(?!.*\.{2})^([a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+(\.[a-z\d!#$%&'*+\-\/=?^_`{|}~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)*|"((([ \t]*\r\n)?[ \t]+)?([\x01-\x08\x0b\x0c\x0e-\x1f\x7f\x21\x23-\x5b\x5d-\x7e\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|\\[\x01-\x09\x0b\x0c\x0d-\x7f\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))*(([ \t]*\r\n)?[ \t]+)?")@(([a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\d\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.)+([a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]|[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF][a-z\d\-._~\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]*[a-z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])\.?$/i;
return expression.test(stringtotest.toLowerCase());
}
// ====================================================================
/** Converts v into a valid int.
*
* @param {value} v - Any value to convert to an int.
*/
function ToInt(v)
{
v = parseInt(v);
if (isNaN(v) || !v)
{
return 0;
}
return v;
}
// ====================================================================
/** Returns the prototype of obj, essentially the JavaScript base class.
*
* @param {object} obj - An object to get the prototype of.
*/
function GetParentObject(obj)
{
return Object.getPrototypeOf(obj);
}
// ====================================================================
/** Limits the number to the range provided.
*
* If value is not a number, then return defaultvalue
*
* @param {number} value - The value to limit.
* @param {number} min - The minimum allowed value.
* @param {number} max - The maximum allowed value.
* @param {number} defaultvalue - The value returned if the original
* value was not a valid number.
*/
function Bound(value, min, max, defaultvalue = 0)
{
if (typeof value != 'number')
{
value = parseFloat(value);
}
if (isNaN(value))
{
value = defaultvalue;
}
if (value < min)
{
value = min;
}
if (value > max)
{
value = max;
}
return value;
}
// ====================================================================
/** Shamelessly stolen from AngularJS to encode HTML entities.
*
* @param {string} str - The string to encode into HTML entities.
*/
function EncodeEntities(str)
{
if (!str)
{
return '';
}
return str.
replace(/&/g, '&').
replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(value) {
let hi = value.charCodeAt(0);
let low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(/([^\#-~| |!])/g, function(value) {
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '<').
replace(/>/g, '>');
}
// ====================================================================
/** Quick and easy regexp to strip HTML tags inside a string.
*
* @param {string} str - The string to encode into HTML entities.
*/
function StripTags(str)
{
if (!str)
{
return '';
}
return str.replace(/(<([^>]+)>)/gi, "");
}
// ====================================================================
/** Returns the extension of a given filename, which is everything
* after the last period.
*
* @param {string} str - The string to encode into HTML entities.
*/
function GetFileExtension(filename)
{
if (!filename)
{
return '';
}
return filename.substr(filename.lastIndexOf('.') + 1);
}
// ====================================================================
/** Merge a number of objects together, with the latter object
* properties overwriting the earlier object properties akin to
* cascading style sheets.
*
* @param {object} A variable number of objects to merge from left to
* right.
*/
function Merge()
{
let newobj = {};
for (let obj of arguments)
{
for (let k in obj)
{
newobj[k] = obj[k];
}
}
return newobj;
}
// ====================================================================
/** Deep clones any object. Handy when you want to use a mutable data type
* without changing the original
*
* Thanks to https://github.com/davidmarkclements/rfdc
*
* @param {object} o - The object to deep clone.
*/
function Clone(o)
{
var refs = []
var refsNew = []
return cloneProto(o);
function CloneCopyBuffer (cur) {
if (cur instanceof Buffer) {
return Buffer.from(cur)
}
return new cur.constructor(cur.buffer.slice(), cur.byteOffset, cur.length)
}
function cloneArray (a, fn) {
var keys = Object.keys(a)
var a2 = new Array(keys.length)
for (var i = 0; i < keys.length; i++) {
var k = keys[i]
var cur = a[k]
if (typeof cur !== 'object' || cur === null) {
a2[k] = cur
} else if (cur instanceof Date) {
a2[k] = new Date(cur)
} else if (ArrayBuffer.isView(cur)) {
a2[k] = CloneCopyBuffer(cur)
} else {
var index = refs.indexOf(cur)
if (index !== -1) {
a2[k] = refsNew[index]
} else {
a2[k] = fn(cur)
}
}
}
return a2
}
function cloneProto (o) {
if (typeof o !== 'object' || o === null) return o
if (o instanceof Date) return new Date(o)
if (Array.isArray(o)) return cloneArray(o, cloneProto)
if (o instanceof Map) return new Map(cloneArray(Array.from(o), cloneProto))
if (o instanceof Set) return new Set(cloneArray(Array.from(o), cloneProto))
var o2 = {}
refs.push(o)
refsNew.push(o2)
for (var k in o) {
var cur = o[k]
if (typeof cur !== 'object' || cur === null) {
o2[k] = cur
} else if (cur instanceof Date) {
o2[k] = new Date(cur)
} else if (cur instanceof Map) {
o2[k] = new Map(cloneArray(Array.from(cur), cloneProto))
} else if (cur instanceof Set) {
o2[k] = new Set(cloneArray(Array.from(cur), cloneProto))
} else if (ArrayBuffer.isView(cur)) {
o2[k] = CloneCopyBuffer(cur)
} else {
var i = refs.indexOf(cur)
if (i !== -1) {
o2[k] = refsNew[i]
} else {
o2[k] = cloneProto(cur)
}
}
}
refs.pop()
refsNew.pop()
return o2
}
}
// ====================================================================
/** Search for any keys containing searchterm inside obj, printing
* the key and value to the console log.
*
* @param {object} obj - The object to search within.
* @param {string} searchterm - A string that the keyname must contain.
*/
function DebugObject(obj, searchterm)
{
let refs = [];
return DebugObjectHelper(obj, searchterm, refs)
function DebugObjectHelper(obj, searchterm, refs)
{
for (let k in obj)
{
let v = obj[k];
if (IsObject(v))
{
if (refs.indexOf(v) != -1)
{
continue;
}
refs.push(v)
DebugObjectHelper(v);
} else if (k.indexOf(search) != -1)
{
L(k, v);
}
}
}
}
// ====================================================================
/** Copy of the python time.time() function, returns the number of
* seconds since the Unix epoch.
*/
function time()
{
return Date.now() / 1000;
}
// ====================================================================
/** Returns a datetime string from a timestamp.
*
* @param {int} timestamp - The timestamp in seconds instead of milliseconds, so
* if you use JavaScript's Date.now(), divided by 1000 first.
* @param {bool} dateonly - Returns only the date string and not a whole
* datetime string.
*/
function PrintTimestamp(timestamp, dateonly = 0)
{
let d = timestamp
? new Date(timestamp * 1000)
: new Date();
let year = d.getFullYear();
let month = d.getMonth() + 1;
month = month < 10 ? '0' + month : month;
let day = d.getDate();
day = day < 10 ? '0' + day : day;
if (dateonly)
{
return year + '-' + month + '-' + day;
}
let hour = d.getHours();
hour = hour < 10 ? '0' + hour : hour;
let minute = d.getMinutes();
minute = minute < 10 ? '0' + minute : minute;
let second = d.getSeconds();
second = second < 10 ? '0' + second : second;
return year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second;
}
// ====================================================================
/** Turns a number of seconds into a hours, minutes, seconds string.
* Handy for countdowns and timers.
*
* @param {number} numseconds - The total number of seconds
* @param {bool} nohours - Set to true to print only minutes and seconds
* @param {bool} precision - The number of digits for fractional seconds.
*/
function SecondsToTime(numseconds, nohours = 0, precision = 0)
{
precision = precision || 0;
if (nohours)
{
if (numseconds <= 0)
{
return '00:00';
}
let minutes = Math.floor(numseconds / 60);
minutes = minutes < 10 ? '0' + minutes : minutes;
let seconds = numseconds % 60;
seconds = seconds < 10 ? '0' + seconds.toFixed(precision) : seconds.toFixed(precision);
return minutes + ':' + seconds;
}
if (numseconds <= 0)
{
return '00:00:00';
}
let hours = Math.floor(numseconds / 3600);
hours = hours < 10 ? '0' + hours : hours;
let minutes = Math.floor((numseconds % 3600) / 60);
minutes = minutes < 10 ? '0' + minutes : minutes;
let seconds = numseconds % 60;
seconds = seconds < 10 ? '0' + seconds.toFixed(precision) : seconds.toFixed(precision);
return hours + ':' + minutes + ':' + seconds;
}
// ====================================================================
/** Converts an UTC ISO date string to local time.
*
* @param {string} isostring - The date to convert in ISO 8601 format
* as an date object or ISO string. If blank, then returns the
* current local time. If it's an integer, then interprets it as
* a timestamp.
*/
function UTCToLocal(isostring)
{
if (typeof isostring == 'object')
{
isostring = MakeDateReadable(isostring)
}
if (typeof isostring == 'number')
{
let d = new Date(parseInt(isostring));
isostring = d.toISOString();
}
if (!isostring)
{
let d = new Date();
d.setMinutes(d.getMinutes() - d.getTimezoneOffset());
return MakeDateReadable(d);
}
isostring = isostring.replace(' ', 'T');
let d = new Date();
let e = new Date(isostring);
if (isNaN(e.getTime()))
{
throw new Error('Invalid date: ' + isostring);
}
e.setMinutes(e.getMinutes() - d.getTimezoneOffset());
return MakeDateReadable(e);
}
// ====================================================================
/** Converts a local date string to UTC time.
*
* @param {string} isostring - The local datetime string to convert
* as a Date object or ISO string. If blank, then returns the
* current UTC time.
*/
function LocalToUTC(isostring)
{
let d = new Date();
if (typeof isostring == 'object')
{
isostring = MakeDateReadable(isostring)
}
if (typeof isostring == 'number')
{
let d = new Date(parseInt(isostring));
return MakeDateReadable(d.toISOString());
}
if (!isostring)
{
return MakeDateReadable();
}
isostring = isostring.replace(' ', 'T');
let e = new Date(isostring + 'Z');
if (isNaN(e.getTime()))
{
throw new Error('Invalid date: ' + isostring);
}
e.setMinutes(e.getMinutes() + d.getTimezoneOffset());
return MakeDateReadable(e);
}
// ====================================================================
/** Makes a ISO timestring more readable by getting rid of the
* characters after seconds and the T between the date and time.
*
* @param {string} timestring - An ISO string, Date object, or
* integer timestamp. If blank, returns the current local time.
*/
function MakeDateReadable(timestring)
{
if (!timestring)
{
timestring = new Date().toISOString();
}
if (typeof timestring == 'number')
{
timestring = new Date(timestring).toISOString();
}
if (!IsString(timestring))
{
timestring = timestring.toISOString();
}
return timestring.substr(0, 19).replace('T', ' ');
}
// ====================================================================
/** Returns a list of the key names for an object
*
* @param {object} obj - The object to list keys for.
*/
function Keys(obj)
{
let keys = [];
for (let k in obj)
{
if (obj.hasOwnProperty(k))
{
keys.push(k);
}
}
return keys;
}
// ====================================================================
/** Returns a list of the values for an object excluding functions.
*
* @param {object} obj - The object to list values for.
*/
function Values(obj)
{
let values = [];
for (let p in obj)
{
switch (typeof obj[p])
{
case 'function':
continue;
};
values.push(obj[p]);
}
return values;
}
// ====================================================================
/** Returns true if the object has a key with the given name.
*
* @param {object} obj - The object to search within
* @param {string} keyname - The key to search for
*/
function HasKey(obj, keyname)
{
return Keys(obj).indexOf(keyname) != -1;
}
// ====================================================================
/** Returns the first property inside an object.
*
* @param {object} obj - The object to look in
*/
function First(obj)
{
for (let k in obj)
{
if (obj.hasOwnProperty(k))
{
return obj[k];
}
}
return '';
}
// ====================================================================
/** Returns the last element of an array or 0 otherwise.
*
* @param {list} a - The array to examine.
*/
function Last(a)
{
if (a.length)
{
return a[a.length - 1];
}
return 0;
}
// ====================================================================
/** Returns true if value is a function.
*
* @param {value} value - The value to examine.
*/
function IsFunc(value)
{
return typeof value === 'function';
}
// ====================================================================
/** Returns true if value is an async function.
*
* @param {value} value - The value to examine.
*/
function IsAsyncFunc(value)
{
return typeof value === 'function' && value.constructor.name == 'AsyncFunction';
}
// ====================================================================
/** Returns true if value is undefined.
*
* @param {value} value - The value to examine.
*/
function IsUndef(value)
{
return typeof value === 'undefined';
}
// ====================================================================
/** Returns true if the value is boolean false, undefined, or is an
* empty array or object.
*
* @param {value} value - The value to examine.
*/
function IsEmpty(v)
{
if (!v || typeof v === 'undefined')
return true;
if (Array.isArray(v))
{
if (v.length == 0)
return true;
} else
{
for (let k in v)
{
if (v.hasOwnProperty(k))
return false;
}
return true;
}
return false;
}
// ====================================================================
/** Returns true if a and b are equal, comparing even lists and objects
*
* @param {value} a
* @param {value} b
*/
function IsEqual(a, b)
{
if ((!a && !b)
|| (typeof a === 'undefined' && typeof b === 'undefined')
|| (IsEmpty(a) && IsEmpty(b))
)
{
return true;
}
if (Array.isArray(a) && Array.isArray(b))
{
if (a.length == b.length)
{
for (let i = 0, l = a.length; i < l; i++)
{
if (a[i] != b[i])
{
return false;
}
}
return true;
} else
{
return false;
}
} else if (typeof a === 'object' && typeof b === 'object')
{
let akeys = Keys(a);
let bkeys = Keys(b);
if (!IsEqual(akeys, bkeys))
{
return false;
}
for (let i = 0, l = a.length; i < l; i++)
{
if (!IsEqual(a[akeys[i]], b[akeys[i]]))
{
return false;
}
}
return true;
}
return a == b;
}
// ====================================================================
/** Converts a dot notation such as animals.felines.cats into
* animals['felines'] = 'cats'
*
* @param {object} baseobj - The object to store the values in
* @param {string} dottedname - The
*/
function SaveNestedObject(baseobj, dottedname)
{
let parts = dottedname.split('.');
for (let i = 0, l = parts.length; i < l; i++)
{
baseobj = baseobj[parts[i]];
}
return baseobj;
}
// =====================================================================
/** Sorts a JavaScript object based upon its keys and returns a sorted
* array of [value, key, obj] pairs. level1 and level2 allow sorting based
* upon a key such as obj['animals']['birds']['parrots']
*
* @param {object} obj - The object to sort
* @param {bool} ignorecase - Set to true to enable case insensitive sorting
* @param {string} level1 - The first key to sort, or blank to do top-level sorting.
* @param {string} level2 - The first key to sort, or blank to do level 1 sorting.
*/
function SortArray(obj, ignorecase, level1 = 0, level2 = 0)
{
let sorted = [];
var key = null;
for (let k in obj)
{
if (typeof obj[k] == "undefined")
continue;
if (level2)
{
if (typeof obj[k][level1] == 'undefined')
continue;
key = obj[k][level1][level2];
} else if (level1)
{
key = obj[k][level1];
} else
{
key = obj[k];
}
if (key == undefined)
{
continue;
}
if (key && ignorecase)
{
key = key.toString().toLowerCase();
}
sorted.push([k, key, obj[k]]);
}
sorted.sort(
function(a, b)
{
if (a[1] == b[1])
{
return 0;
}
return (a[1] > b[1]) ? 1 : -1;
}
);
return sorted;
}
// ====================================================================
/** Copies all keys and values from newvalues to obj.
*
* Useful for storing multiple CSS styles in a DOM element.
*
* @param {object} objfrom
* @param {object} objto
*/
function CopyValues(copyfrom, copyto)
{
for (let k in copyfrom)
{
if (!k)
{
continue;
}
copyto[k] = copyfrom[k];
}
}
// ====================================================================
/** Simple regex to get the filename portion of a full path.
*
* Thanks to http://planetozh.com/blog/2008/04/javascript-basename-and-dirname/
*
* @param {string} path - The full path of the file.
*/
function Basename(path)
{
return path.replace(/\\/g,'/').replace( /.*\//, '' );
}
// ====================================================================
/** Simple regex to get the directory name portion of a full path.
*
* Thanks to http://planetozh.com/blog/2008/04/javascript-basename-and-dirname/
*
* @param {string} path - The full path of the file.
*/
function Dirname(path)
{
return path.replace(/\\/g,'/').replace(/\/[^\/]*$/, '');;
}
// ====================================================================
/** Returns true if the string starts with the prefix.
*
* @param {string} prefix - The prefix to test with.
*/
String.prototype.startswith = function(prefix)
{
return this.indexOf(prefix) == 0;
};
// ====================================================================
/** Returns true if the string ends with the suffix
*
* @param {string} suffix - The suffix to test with.
*/
String.prototype.endswith = function(suffix)
{
return this.indexOf(suffix, this.length - suffix.length) !== -1;
};
// ====================================================================
/** Returns a random character from this string.
*/
String.prototype.random = function()
{
if (!this.length)
{
return '';
}
return this.charAt(RandInt(0, this.length));
};
// ====================================================================
/** Replaces all instances of search with replace inside of this string.
*
* Thanks to http://dumpsite.com/forum/?topic=4.msg29#msg29
*
* @param {string} search - The original string
* @param {string} replace - The replacement string
* @param {bool} ignorecase - Set to true to use case insensitive replacing
*/
String.prototype.replaceAll = function(search, replace, ignorecase = 0)
{
return this.replace(new RegExp(search.replace(/([\/\,\!\\\^\$\{\}\[\]\(\)\.\*\+\?\|\<\>\-\&])/g,"\\$&"),(ignorecase?"gi":"g")),(typeof(replace)=="string")?replace.replace(/\$/g,"$$$$"):replace);
};
// ====================================================================
/** Capitalizes every beginning letter in the string. Note that this
* is a simple function and doesn't skip smaller words like the, in, and
* so forth, so it's not quite the same as title case.
*
* Thanks to http://stackoverflow.com/questions/2332811/capitalize-words-in-string/7592235#7592235
*/
String.prototype.capitalize = function()
{
return this.replace(
/(?:^|\s)\S/g,
function(a)
{
return a.toUpperCase();
}
);
};
// ====================================================================
/** Returns true if the string contains the given value.
*
* @param {value} value - The value to test for.
*/
String.prototype.has = function(value)
{
return this.indexOf(value) != -1;
};
// ====================================================================
/** Removes any element from this array that has the same value as the
* given value.
*
* Thanks to http://stackoverflow.com/questions/3954438/remove-item-from-array-by-value
*
* @param {value} value - The value to test for equivalence
*/
Array.prototype.remove = function()
{
let what, a = arguments, L = a.length, ax;
while (L && this.length)
{
what = a[--L];
while ((ax = this.indexOf(what)) !== -1)
{
this.splice(ax, 1);
}
}
return this;
};
// ====================================================================
/** Returns true if the array contains the given value.
*
* @param {value} value - The value to test for.
*/
Array.prototype.has = function(value)
{
return this.indexOf(value) != -1;
};
// ====================================================================
/** A copy of the python extend function, this appends the given array
* to the current aray.
*
* @param {array} a - The array to append
*/
Array.prototype.extend = function (a)
{
a.forEach(function(v) {this.push(v)}, this);
}
// ====================================================================
/** Moves an array element from one position to another. Great for
* rearranging tables or lists.
*
* Thanks to http://stackoverflow.com/questions/5306680/move-an-array-element-from-one-array-position-to-another
*
* @param {int} frompos - The position to move from
* @param {int} topos - The position to move to
*/
Array.prototype.move = function(frompos, topos)
{
while (frompos < 0)
{
frompos += this.length;
}
while (topos < 0)
{
topos += this.length;
}
if (topos >= this.length)
{
let k = topos - this.length;
while ((k--) + 1)
{
this.push(undefined);
}
}
this.splice(topos, 0, this.splice(frompos, 1)[0]);
return this; // for testing purposes
};
// ====================================================================
/** Randomly shuffles an array's elements. Be sure to clone a copy if
* you still want to use the original array.
*
* @param {array} a - The array to shuffle.
*/
function Shuffle(a)
{
let i = a.length, j, tempi, tempj;
if (i == 0) return false;
while (--i)
{
j = Math.floor(Math.random() * (i + 1));
tempi = a[i];
tempj = a[j];
a[i] = tempj;
a[j] = tempi;
}
return a;
}
// ====================================================================
/** A copy of the C strcmp() function.
*
* @param {string} a
* @param {string} b
* @param {bool} ignorecase
*/
function StrCmp(a, b, ignorecase = 0)
{
if (ignorecase)
{
a = a.toUpperCase();
b = b.toUpperCase();
}
if (a > b)
{
return 1;
}
if (b > a)
{
return -1;
}
return 0;
}
// ====================================================================
/** A copy of the C strcmpi() function.
*
* @param {string} a
* @param {string} b
*/
function StrCmpI(a, b)
{
return StrCmp(a, b, 1);
}
// ====================================================================
/** Returns a random integer from min to max.
*
* @param {int} min - The minimum value
* @param {int} max - The maximum value
*/
function RandInt(min, max)
{
return Math.round(Math.random() * (max - min)) + min;
}
// ====================================================================
/** Returns a random element of an array.
*
* @param {array} a
*/
function RandElement(a)
{
return a[RandInt(0, a.length - 1)];
}
// ====================================================================
/** A copy of the python range() function, this returns an array of
* integers from min to max differing by step.
*
* @param {int} min
* @param {int} max
* @param {int} step
*/
function Range(min, max, step)
{
let seq = Array();
for (let i = min; i < max; i += step)
{
seq.push(i);
}
return seq;
}
// ====================================================================
/** Returns true if v is a string.
*
* @param {value} v
*/
function IsString(v)
{
return (typeof v == 'string' || v instanceof String);
}
// ====================================================================
/** Returns true if v is a number.
*
* @param {value} v
*/
function IsNum(v)
{
return typeof v == 'number';
}
// ====================================================================
/** Returns true if v is an array.
*
* @param {value} v
*/
function IsArray(v)
{
return Array.isArray(v);
};
// ====================================================================
/** Returns true if v is an object.
*
* @param {value} v
*/
function IsObject(obj)
{
return obj === Object(obj);
}
// ====================================================================
/** Converts a dict into query string params. Useful if you need to
* construct a query string.
*
* @param {object} data - The dict to convert
*/
function ToParams(data)
{
let params = [];
for (let p in data)
{
if (data.hasOwnProperty(p))
{
let k = p, v = data[p];
params.push(typeof v == "object"
? ToParams(v, k) :
encodeURIComponent(k) + "=" + encodeURIComponent(v));
}
}
return params.join("&");
}
// ====================================================================
/** Generic function to see if the point (x, y) is inside the rect
* (top, right, bottom, left)
*
* @param {number} x
* @param {number} y
* @param {rect} rect
*/
function InRect(x, y, rect)
{
if (!rect)
return 0;
return (rect.left <= x
&& x <= rect.right
&& rect.top <= y
&& y <= rect.bottom);
}
// ====================================================================
/** Convenience function to set a cookie in JavaScript. Pafera itself
* uses this to set a user's time offset for converting between
* GMT and local time on the server side. Only name and value are
* required.
*
* @param {string} name - The identifier for the cookie
* @param {value} value - The cookie's contents
* @param {int} numdays - How many days the cookie is valid for
* @param {string} path - The valid path for the cookie
* @param {string} domain - The valid domain for the cookie
* @param {string} secure - The security for the cookie
*/
function SetCookie(name, value, numdays, path, domain, secure)
{
path = path || '/';
let now = new Date();
if (numdays)
{
numdays = numdays * 1000 * 60 * 60 * 24;
}
let expirationdate = new Date(now.getTime() + numdays);
document.cookie = name + '=' + escape(value)
+ (numdays ? ';expires=' + expirationdate.toGMTString() : '' )
+ ';path=' + path
+ (domain ? ';domain=' + domain : '')
+ (secure ? ';secure' : '')
+ '; SameSite=Strict';
}
// ====================================================================
/** Taken from mysql-connector for the web dbapi where we know that
* only administrators will use this function.
*
* @params {string} s - The SQL query fragment to escape
*/
function EscapeSQL(s)
{
let ls = [];
for (let i = 0, l = s.length; i < l; i++)
{
switch (s[i])
{
case '\\n':
ls.push('\\\\n')
break;
case '\\r':
ls.push('\\\\r')
break;
case '\\':
ls.push('\\\\')
break;
case "'":
ls.push("\\'")
break;
case '"':
ls.push('\\"')
break;
case '\u00a5':
case '\u20a9':
// escape characters interpreted as backslash by mysql
// fall through
default:
ls.push(s[i])
}
}
return ls.join('');
}
// ====================================================================
/** An implementation of other languages' sleep functions through
* JavaScript promises.
*
* @param {int} ms - The number of milliseconds to delay execution.
*/
function Sleep(ms)
{
return new Promise(resolve => setTimeout(resolve, ms));
}
// ====================================================================
// Fake function for JSDoc below
//
/** Defines a uuid() function that can be used to return unique IDs.
*
* Thanks to https://github.com/jchook/uuid-random/blob/master/uuid-random.min.js
*/
function uuid()
{
}
!function(){function g(a,b){return Math.floor(Math.random()*(b-a))+a}function h(c){let f;if("undefined"!=typeof e){if("undefined"==typeof a||b+c>j.BUFFER_SIZE)if(b=0,e.getRandomValues)a=new Uint8Array(j.BUFFER_SIZE),e.getRandomValues(bytes);else{if(!e.randomBytes)throw new Error("Non-standard crypto library");a=e.randomBytes(j.BUFFER_SIZE)}return a.slice(b,b+=c)}for(f=[],d=0;d<c;d++)f.push(g(0,16));return f}function i(){let a=h(16);return a[6]=15&a[6]|64,a[8]=63&a[8]|128,a}function j(){let a=i();return c[a[0]]+c[a[1]]+c[a[2]]+c[a[3]]+"-"+c[a[4]]+c[a[5]]+"-"+c[a[6]]+c[a[7]]+"-"+c[a[8]]+c[a[9]]+"-"+c[a[10]]+c[a[11]]+c[a[12]]+c[a[13]]+c[a[14]]+c[a[15]]}let a,d,b=0,c=[];for(j.BUFFER_SIZE=512,j.bin=i,d=0;d<256;d++)c[d]=(d+256).toString(16).substr(1);if("undefined"!=typeof module&&"function"==typeof require){let e=require("crypto");module.exports=j}else"undefined"!=typeof window&&(window.uuid=j)}();
// ====================================================================
// Fake function for JSDoc below
//
/** Defines a SVGInject() function that replaces an img tag with inline
* SVG, allowing CSS and JavaScript to be used on the SVG
*
* Use like
* <img src="image.svg" onload="SVGInject(this)">
*
* Thanks to https://github.com/iconfu/svg-inject/
*
* @params {DOMElement} imgtag - The image tag to replace with SVG
*/
function SVGInject(imgtag)
{
}
/* MIT License - blob/master/LICENSE */
!function(r,l){var p,h=null,g="length",n="SVG_NOT_SUPPORTED",y="LOAD_FAIL",b="SVG_INVALID",i="createElement",S="__svgInject",s=["src","alt","onload","onerror"],A=l[i]("a"),o=l[i]("div"),k="undefined"==typeof SVGRect,a={cache:!0,copyAttributes:!0,makeIdsUnique:!0},w={clipPath:["clip-path"],"color-profile":h,cursor:h,filter:h,linearGradient:["fill","stroke"],marker:["marker","marker-end","marker-mid","marker-start"],mask:h,pattern:["fill","stroke"],radialGradient:["fill","stroke"]},I=1,f=2,c=3;function j(e,t,r,n,i){if(t=t||L(r,n)){var o=e.parentNode;if(o){i.copyAttributes&&function u(e,t){for(var r=e.attributes,n=0;n<r[g];++n){var i=r[n],o=i.name;if(-1==s.indexOf(o)){var a=i.value;if("title"==o){var f=l.createElementNS("http://www.w3.org/2000/svg","title");f.textContent=a;var c=t.firstElementChild;c&&"title"==c.tagName.toLowerCase()?t.replaceChild(f,c):t.insertBefore(f,c)}else t.setAttribute(o,a)}}}(e,t),i.makeIdsUnique&&function b(e){var t,r,n,i,o,a,f,c,u,l=e.querySelectorAll("defs>[id]"),s={};for(f=0;f<l[g];f++)if((r=(t=l[f]).tagName)in w)for(i=(n=t.id)+"-"+Math.random().toString(36).substr(2,10),t.id=i,o=w[r]||[r],c=0;c<o[g];c++)(s[a=o[c]]||(s[a]=[])).push([n,i]);var d=Object.keys(s);if(d[g]){var v,m,p,h,y=e.querySelectorAll("*");for(f=0;f<y[g];f++)for(v=y[f],c=0;c<d[g];c++)if(m=d[c],p=v.getAttribute(m))for(h=s[m],u=0;u<h[g];u++)if(p.replace(/"/g,"")=="url(#"+h[u][0]+")"){v.setAttribute(m,"url(#"+h[u][1]+")");break}}}(t);var a=i.beforeInject&&i.beforeInject(e,t)||t;o.replaceChild(a,e),e[S]=f,T(e),i.afterInject&&i.afterInject(e,a)}}else x(e,i)}function E(){for(var e={},t=arguments,r=0;r<t[g];++r){var n=t[r];if(n)for(var i in n)n.hasOwnProperty(i)&&(e[i]=n[i])}return e}function L(e,t){try{o.innerHTML=e}catch(n){return h}var r=o.firstElementChild;if(o.innerHTML="",function i(e){return e instanceof SVGElement}(r))return r.setAttribute("data-inject-url",t),r}function T(e){e.removeAttribute("onload")}function u(e,t,r){e[S]=c,r.onFail&&r.onFail(e,t)}function x(e,t){T(e),u(e,b,t)}function C(e,t){T(e),u(e,n,t)}function G(e,t){u(e,y,t)}function N(e){e.onload=h,e.onerror=h}function O(){throw new Error("img not set")}var e=function M(e,t){var d=E(a,t),v={};function m(o,a){if(o){var e=o[g],t=o.src;if(t&&!o[S]){if(o[S]=I,a=E(d,a),k)return void C(o,a);var f=function l(e){return A.href=e,A.href}(t),n=a.cache,c=function(e){if(n){for(var t=v[f],r=0;r<t[g];++r)t[r](e);v[f]=e}};if(N(o),n){var r=v[f],i=function(e){e===y?G(o,a):e===b?x(o,a):j(o,h,e,f,a)};if(r!==undefined)return void(Array.isArray(r)?r.push(i):i(r));v[f]=[]}!function s(e,t,r){if(e){var n=new XMLHttpRequest;n.onreadystatechange=function(){if(4==n.readyState){var e=n.status;200==e?t(n.responseXML,n.responseText.trim()):400<=e?r():0==e&&r()}},n.open("GET",e,!0),n.send()}}(f,function(e,t){if(o[S]==I){var r=e instanceof Document?e.documentElement:L(t,f);if(r){var n=a.afterLoad;n&&(n(r),t=function i(){return p=p||new XMLSerializer}().serializeToString(r)),j(o,r,t,f,a),c(t)}else x(o,a),c(b)}},function(){G(o,a),c(y)})}else if(e)for(var u=0;u<e;++u)m(o[u],a)}else O()}return function n(e){var t=l.getElementsByTagName("head")[0];if(t){var r=l[i]("style");r.type="text/css",r.styleSheet?r.styleSheet.cssText=e:r.appendChild(l.createTextNode(e)),t.appendChild(r)}}('img[onload^="'+e+'("]{visibility:hidden;}'),m.setOptions=function(e){d=E(d,e)},m.create=M,m.err=function(e,t){e?e[S]!=c&&(N(e),k?C(e,d):(T(e),G(e,d)),t&&(T(e),e.src=t)):O()},r[e]=m}("SVGInject");"object"==typeof module&&"object"==typeof module.exports&&(module.exports=e)}(window,document);
// ====================================================================
/** Convert a 32-bit integer into a six character alphanumeric code.
*
* This is used extensively in Pafera to both save space in URLs and to
* make IDs more human readable.
*
* Since JavaScript represents all numbers internally as 64-bit doubles,
* if you need a 64-bit integer, use two short codes together.
*
* @param {int} val - The integer to convert
* @param {string} chars - The character set to use. Make sure that
* every application that you write uses the same character set, or
* short codes will not be portable.
*/
function ToShortCode(
val,
chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
)
{
val = val + 2147483648
let base = chars.length;
let code = '';
let mod = 0;
for (;;)
{
mod = parseInt(val % base);
code = chars[mod] + code;
val = (val - mod) / base;
if (val <= 0)
{
break
}
}
while (code.length < 6)
{
code = '0' + code;
}
return code
}
// =====================================================================
/** The reverse of ToShortCode(), turning a six character alphanumeric
* code into a 32-bit integer
*
* @param {string} code - The code to convert
* @param {string} chars - The character set to use. Make sure that
* every application that you write uses the same character set, or
* short codes will not be portable.
*/
function FromShortCode(
code,
chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_'
)
{
let base = chars.length;
//code = code.rstrip('0')
let l = code.length
let val = 0;
chars = chars.split('');
let arr = {};
for (let i = 0; i < base; i++)
{
arr[chars[i]] = i
}
for (let i = 0; i < l; i++)
{
val = val + (arr[code[i]] * (base ** (l - i - 1)))
}
val = val - 2147483648
return val
}
// =====================================================================
/** For simple highlighting of values, returns the background color
* for a value based upon given thresholds.
*
* The colors run from purple, blue, green, orange, to red, so
* thresholds needs to be an array of four values from highest to lowest.
*
* @param {number} value - The value to test
* @param {array} thresholds - A list of four values from highest to lowest.
*/
function ColorCode(value, thresholds)
{
let color = 'dredg';
if (value >= thresholds[0])
{
color = 'dpurpleg';
} else if (value >= thresholds[1])
{
color = 'dblueg';
} else if (value >= thresholds[2])
{
color = 'dgreeng';
} else if (value >= thresholds[3])
{
color = 'dorangeg';
}
return color;
}
// ********************************************************************
// DOM Convenience functions
// ====================================================================
/** Returns an element by ID.
*
* @param {string} id - CSS ID selector
*/
var I = function(id)
{
return document.getElementById(id);
}
// ====================================================================
/** Returns all elements containing this selector.
*
* @param {string} selector - CSS selector
*/
var Q = function(selector)
{
if (IsString(selector))
{
if (selector[0] == '#')
{
return [ I(selector.substr(1)) ];
}
return document.querySelectorAll(selector);
}
if (IsArray(selector) || selector instanceof NodeList)
{
return selector;
}
if (selector)
{
return [selector];
}
return [];
}
// ====================================================================
/** Returns the first element containing this selector.
*
* @param {string} selector - CSS selector
*/
var E = function(selector)
{
return IsString(selector)
? document.querySelector(selector)
: selector;
}
// ====================================================================
/** Convenient alias for console.log
*
* @param {args} args - Anything that console.log will accept.
*/
var L = console.log;