akkd-common

Common functions

当前为 2023-09-04 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/474549/1245473/akkd-common.js

  1. // ==UserScript==
  2.  
  3. // #region Info
  4.  
  5. // @namespace https://greasyfork.org/en/users/1123632-93akkord
  6. // @exclude *
  7. // @author Michael Barros (https://greasyfork.org/en/users/1123632-93akkord)
  8. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNvyMY98AAA5DSURBVGhDtVr5T1zXGeVviPJDq/bHNq2ZN8MyrGEx4C2JY6tqo+SXRGoVKYqqSlXaqFUjNUlTRXWUWE5iXC/YmNWAjdmXGRgDNruNYciwG4aBYR0wY+x4AWbe1/PdN282PwO21HGO333bfed893zfve85Efx7q6zrpd9UDX32er295bDZ2f6GeVbgdZMT4G1Q2+zwbWfbD6F9SGydvu1s+0GcU3HAh5B2fTgc7QeALLSzGmfbM4GsRuwD+8RWOZaB84y06smW/RUTnx7MMb0kyB860/jT1xrvjO+3LFGWZZmymhcps3kB4C3vL9E+XzsT7UzLImU0LVEGjqVb5gFscVy0cV9a0wKlmucFXjUvUopZ2ed2MpCE/cRwmOYpqWGREoGERmwbcaxxnuJxPN60II7FNywIxKFtNK1Qaq19ZG92608iDtYO5WSBRNa1ZcpgQEQGxDCYNO/vBQTJHZAGpEIckw1HcpMiIBmEkkxLfiSCEBPUQrwpgDiIMjYuUYxpmWLNAO5NLx/PicismZrLsLhE9DMF+TABwN4wolpIw4ikgmTKjgIwCir55xKAyPsEGCGAkVA1MRuRWe9wZzSv+CzyNIRdAC3SwUiFfVJgHy3yDFVAcPS1SAcjOPpxIM8CYnkLEXEQEFcz5Y7IUgXARsLjKnGArbOb6DPYOikgqUWeoWUhLdKM4MirApi8H75RSKix7yxAi6wWhIAw0sF4UQFK9BeUyAcJ4DxIqMYI7K+3uzObXX7iDBH5XZIX3od9tEgzBHGAq08S9lXyuxGgkg8XwO0Y3B9fBQH7ICDDJ2C3CasiDYme6kteLfIMvwAQEsCx5xPA20VBWkWMELAMAWyhBghAFRKR1yC5HRQBin20yDMCApg0C9i9hQICAuQDgIAKhxtVaNK9F+S1CD4LPFmlgdTufL/w3LVfbW8nwCgEzLyoALXmaxNXoQgAOPLPIUAAbVWA6vtQAZwDYgSmnluAv2Q28RJh+wROMgeIP5cAgAUYcSycPCewP4l3OwK7tQ1DjbwgzFuI1CKrBZW4gkDV0RYwqQhI36UAts12FUfF/1uACiEgYwcBXGlEtQmxzdOkVTB5tR1sGy2ywQi3DkOLdDB8AqZ2ELCMycoF8iwCiauuebD/qtlFqajHDEGaI++LfrKPeDLO8Qgkm+boVZBKQvVIwHGxkGNhPtJCBKCQ5y28j+MxAFecuIYlHzAL7yRA+B1E0zFJHb3uoqNtynkeAbFo45nX5KTqmfv0w+ojKpx0UXLDrCiZwXVeRB4l9JOBVbLefUgDdx/RG/UOsfYPnH+WAJDEvTF4L/hL3yIN4l6+/6jJIcizMDGRZWAtxETTEWWOdBomNcUu8/Rep5Mey0SbRPRp7xyi5xTR57qeXDdLI2uPCaepY85FUUUDlFg/I84FC0isX6DjtmXy4rot4LXLt8hYOY3VpM8+PgEKEGGQM6LN5IWIeid9YV0Uz9nEX0eqhshYNYXzeNkRAuocbl7PpGEU0ppgB0EeD4fyCxNruE35tcy4yFDSBxs4QRJvULUzNIyICAFOF0m5PaQrGaSEBuQIHpwMCFJ1c/Q1CPDPAxws7SF98QAZq+/4vK++dYE87BYHESwgxjwHu6CMVjvo84EFkmUvbeFhh8ut4DFEUTXTEIAyml7rcLM1uDSqEH6uddD4+gYJhsD61hal5neQvnQIr34zlIQOhlYf+gXoLnSRruA2RV8aJIwq7aufpfRGPKTGTl/1z4lOPMCB4i4I6KOokn5KqR7DtTOUiWuT6+cFcUY8bJTR6KDfWpy0t2qE/tXH92ME8bAjLOCSjfZcGaPYKyPuiFQI4MkoFTf6yaODd6/N0hMZj8RNMv7y4s/HljGKzMPDy2yUUDFOw34By6Q/30VSwS26MOKkex6Z3F6ZTlqn5JjSH+ir27O4DhEUAjrJUNhLHzQN0+qml9bRwa3V+5RZOYJknaFUiM8bWxV98PXr2I6sPRAC0KQ3Lw+QoXgQAoZJX2LVFpCAhDw94hKPnFt/IPc55mXCEJqmFkm62CmISgU3aWjlR3QrU+fsMkWfa6Fsq528uI5RPmz3Rp+7Lu+52EvHeibFdTwC+ws76L2afnI/AT0Exu5+KB8qaJP1Rf1kuDJEZ8dWxHUcuU3gsdcr9jlQqoCoYivpcG2IABF5QCQhhn3kHicoiFinvMeuDcgIKN19vEnJBbBAQQ9F5naSzXWfA0NdM4t0vGcMyc53EJlGZ7zRZ8xe6WIHRV7spv90T4jrtkDoT9W95EI/TMZ1/0f5SH6rR3+xiyIL+yi1tJ/WNjhTvDSwuCof/G/lVkZ29Vbr9Dx3GyRgkKTwEVAFcPK+Y3HAPmwamf5Y2e19M9dCG2wjJtAE5XndpDvfTkOude6X7m8wITwDD+hyLHhjTpm8ugsdZMB1Ul4PHWMBOIf/aH1DifzKoyf0O5CXzt/AaPaQVHSLPjSNKpbFk/9ce1OWTlso8vQ1+mfrkDiuCtAjz0IF8MTE0cfCK7HBSdlDKFu4YxOkznUOyd/esNH9TcQXHVSMz5H+QjtJOTdo2HVPYeX78UAX2ablyJxWIdKQ30sScKxLGQH/D/c43A8oLbcdOYXrim+LxP779SnRHY/B0bJ20uXewEi30+cdd8RtqgAJ1U4qH0YyswCU0RSUUfY/z5BJSCKb+yFGUYzaU7/5xxuUgI6lnOs0vBwQ8ACjwKI9wD/aRklf2ENRhUquHOtWCPCIPsQIsJWYZOvsKkXhfNQlK2zRTx9fGxfHeb54pwLFIh8lN6+XvujkHFIEHLliJQmFgUdACEiBgGSenCCAZ8K3mh2wC5OXaXzprrdzet7TMb3gsc6tIA0Uj79fP0i6nDayCQEyTbrW5H0ny7Ym7q7jTpnWUF3erkC1KLqJfMEIqAJA4PdFTVv5N0fZjejLS9+hQhlAngW8c7WPNvgp6OWbW1MkIS+kgn46dtMu4sTC3kQZVQQgSEJArd0tpn/YJ75+jk7YlElnCx0dPGP26I5XyhKQ8F29d+0Jz8lEpaNOMpxtoUEIYEkd9gV5z7d19HbhNe/DTXgcKifcP1JKaS9s1OXPAa7j+842yTHf18jWxRXWQI/wnA8tmJiK+siIwjDkWhMjeW/TQ6dQ1U72zdLqI8xH6IAFvHEVFajUBgFjsBIEvFrjcCci+rywiqudxprlEXOkkZV78p7sGvr1KQvtQTL9KtuMMqpMKAsPNsh45poQwKW2bXaJ9KfMpMu20Il2m5dtxOSq7asUndtLxzkHcIAtcCDvOhlON9ORglavSGiInQfBQ1d6hNh3r/TK95BvXDp5xGTctIX5iIsp93u4oh8jgAQun8CkOsgCZhQBsM/euikqGJjyFg9MeP5ad9OrQ4XQ53aRgcscqs77Vb1ykXXSkzcw6dmfd0P+un3YW2S94/mybcgbhZzQc0U53SSf7B7xXMJ1BcDh8pvyH6r75IKBO558XJuc3ylK6x4Ugg9rur3FuKbIOuX5pGVYjobn9+R10eGCZk9J/4Snd8blvYrzH1V0eopwfyHa6aWw1eWxYAEOWMj3RRjrm1isM3TnWlEmQQYPYg9HFcLLXFEuYBI710bS2TYyMGG0BXLacR4Jh4SMhGAujXq+5hy2EC/BGhKukc53wNPoB8mtB6IwexvOtWOL80WwGy8xYCUpF8/F9bqTzbLhdIvvOddxXTdyhf0P+6gCkqodbl4RipUh1iOxdQ4ylo+L5NEjicQMiQQzFN0WFUWX302RIKsTwHyALR/n83ydHlsdCwFRHSch9wHouCJha8BCzoA6rkcp5L6lQvQL6EtwHMlpgL/1l/g4RBbwvXwfAghxhhL4//II6Zjf1Tt8vTsisdoOAYFXPv4Gn9CAlSAWayhTYuXI4AdzpeCaLUgIoHpcwnEug6WDAYCcAYgqsQE/iGOiLyZ9GRNhmQ9oS1hX6QDDZZRFkNNzdbk8inYopCsgLcDRHyd9BQSUQUAS1tQJsI8qIBjGOqdIGAkkWL0gBiIKuA1ygmQo9BzFcDBxkNQzySBwOZTKgwmGgqOtQro6IWBA9PUVkwEB7H8tAQLICz1EcESVIeaIMnFt8gYghDiiKyDIKwKkYAgBiKoGeYEwARx5Ju8XkLyTAMCIty8DD7mPlBZxRjh5cY+IPCMo6oDwMrfZElrEVfjJB4iHCAjPAUbgFS8AI146+MHhAjjpGE8LCCUdTpyhLwN5lERN4oAg7o98KPkQAeo/pG0nIN48T3F1sBMeHC5AFaW2BUT0txOAZBUCQkkHI9j3OwrQJB0E9VuNERUqumyEohFhRihpIMwuKoIjL5J2F4mrZRuByiky4MU+uszmjkjAi3GwAIWo8nUgFPx9RkE8EjsKQ294UQHC99rW2ck2AhCgDwjgEQiNsraAoM98EByDnIjiqvLCAkKJq9jJNgLBAuIr7G7lQxIT931Q2iWi6+ZBCGR4rmCUhdb5ENLCNtsTV8kHl8pQgHiFHQKmIWAaAoYDApgQR3i7j6laMNY78YINYiAvITe0BajkdxP57cj7UAkRVXYIGIWFKibmOPoxjfzNMfDdcdeA9aKR2KJec2V5SsAosLvIP5s8A6SFALQrAVgo5pLVGRF9sTc3FgKYPP/jmSbJnYCciMVkJ10FEV+F4TWNUmmeTV7Ab5tgsloICJCqsV/lwOr0xvmIX/zt2M+ji/sn+csv/6OBMgrPCb6PRcBO+gpE0i9gB/IMLIt3K0ACDLBOFPwfe773zi8/Ov4z8X+svPLBNy8bjtd8GZXbbonJ7+18EURf7OrRn2m16k+1WA3Zptv6E3WD0vfm29KJepv03dPQAZHfY3uywSadbNwWOgGTLTK72Rp9utUifVP571c++OzliIiIiP8B4VfIttF+iOkAAAAASUVORK5CYII=
  9.  
  10. // #endregion Info
  11.  
  12. // ==UserLibrary==
  13.  
  14. // @name akkd-common
  15. // @description Common functions
  16. // @copyright 2022+, Michael Barros (https://greasyfork.org/en/users/1123632-93akkord)
  17. // @license CC-BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/legalcode
  18. // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0.txt
  19. // @version 0.0.19
  20.  
  21. // ==/UserScript==
  22.  
  23. // ==/UserLibrary==
  24.  
  25. // ==OpenUserJS==
  26.  
  27. // @author 93Akkord
  28.  
  29. // ==/OpenUserJS==
  30.  
  31. /// <reference path='C:/Users/mbarros/Documents/DevProjects/Web/Tampermonkey/Palantir/@types/__fullReferencePaths__.js' />
  32.  
  33. /*
  34.  
  35. # akkd-common
  36.  
  37. A collection of commonly used classes and functions.
  38.  
  39. ## Required requires:
  40.  
  41. - https://code.jquery.com/jquery-3.2.1.min.js
  42. - https://greasyfork.org/scripts/474546-loglevel/code/loglevel.js?version=1245469
  43.  
  44. */
  45.  
  46. // #region Events
  47.  
  48. // Setup location change events
  49. /**
  50. *
  51. * Example usage:
  52. * ```javascript
  53. * window.addEventListener('popstate', () => {
  54. * window.dispatchEvent(new Event('locationchange'));
  55. * });
  56. */
  57. (() => {
  58. class LocationChangeEvent extends Event {
  59. constructor(type, prevUrl, newUrl) {
  60. super(type);
  61.  
  62. this.prevUrl = prevUrl;
  63. this.newUrl = newUrl;
  64. }
  65. }
  66.  
  67. let prevUrl = document.location.href;
  68. let oldPushState = history.pushState;
  69.  
  70. history.pushState = function pushState() {
  71. let ret = oldPushState.apply(this, arguments);
  72. let newUrl = document.location.href;
  73.  
  74. window.dispatchEvent(new LocationChangeEvent('pushstate', prevUrl, newUrl));
  75. window.dispatchEvent(new LocationChangeEvent('locationchange', prevUrl, newUrl));
  76.  
  77. prevUrl = newUrl;
  78.  
  79. return ret;
  80. };
  81.  
  82. let oldReplaceState = history.replaceState;
  83.  
  84. history.replaceState = function replaceState() {
  85. let ret = oldReplaceState.apply(this, arguments);
  86. let newUrl = document.location.href;
  87.  
  88. window.dispatchEvent(new LocationChangeEvent('replacestate', prevUrl, newUrl));
  89. window.dispatchEvent(new LocationChangeEvent('locationchange', prevUrl, newUrl));
  90.  
  91. prevUrl = newUrl;
  92.  
  93. return ret;
  94. };
  95.  
  96. window.addEventListener('popstate', () => {
  97. let newUrl = document.location.href;
  98.  
  99. window.dispatchEvent(new LocationChangeEvent('locationchange', prevUrl, newUrl));
  100.  
  101. prevUrl = newUrl;
  102. });
  103. })();
  104.  
  105. // #endregion Events
  106.  
  107. // #region Helper Classes
  108.  
  109. class Logger {
  110. /**
  111. * Creates an instance of Logger.
  112. *
  113. * @author Michael Barros <michaelcbarros@gmail.com>
  114. * @param {Window} _window
  115. * @param {string | null} devTag
  116. * @memberof Logger
  117. */
  118. constructor(_window = null, devTag = null) {
  119. /**
  120. * @type {Window}
  121. * @private
  122. */
  123. this.window = _window || getWindow();
  124.  
  125. /** @type {string | null} */
  126. this.devTag = devTag;
  127.  
  128. /** @type {string[]} */
  129. this._additionalTags = [];
  130. }
  131.  
  132. /**
  133. *
  134. * @author Michael Barros <michaelcbarros@gmail.com>
  135. * @type {string[]}
  136. * @public
  137. * @memberof Logger
  138. */
  139. get additionalTags() {
  140. return this._additionalTags;
  141. }
  142.  
  143. /**
  144. *
  145. *
  146. * @author Michael Barros <michaelcbarros@gmail.com>
  147. * @param {string[]} value
  148. * @memberof Logger
  149. */
  150. set additionalTags(value) {
  151. if (getType(value) != 'array') {
  152. value = [value];
  153. }
  154.  
  155. this._additionalTags = value;
  156. }
  157.  
  158. /** @type {string} */
  159. get label() {
  160. return [].concat([this.devTag], this.additionalTags).join(' ');
  161. }
  162.  
  163. /** @type {(...data: any[]) => void} */
  164. get log() {
  165. if (this.devTag) {
  166. return console.log.bind(console, `${this.label}`);
  167. } else {
  168. return console.log.bind(console);
  169. }
  170. }
  171.  
  172. /** @type {(...data: any[]) => void} */
  173. get info() {
  174. if (this.devTag) {
  175. return console.info.bind(console, `${this.label}`);
  176. } else {
  177. return console.info.bind(console);
  178. }
  179. }
  180.  
  181. /** @type {(...data: any[]) => void} */
  182. get error() {
  183. if (this.devTag) {
  184. return console.error.bind(console, `${this.label}`);
  185. } else {
  186. return console.error.bind(console);
  187. }
  188. }
  189.  
  190. /** @type {(...data: any[]) => void} */
  191. get debug() {
  192. if (this.devTag) {
  193. return console.debug.bind(console, `${this.label}`);
  194. } else {
  195. return console.debug.bind(console);
  196. }
  197. }
  198.  
  199. /** @type {(...data: any[]) => void} */
  200. get warn() {
  201. if (this.devTag) {
  202. return console.warn.bind(console, `${this.label}`);
  203. } else {
  204. return console.warn.bind(console);
  205. }
  206. }
  207.  
  208. /**
  209. * Maybe use later?
  210. *
  211. * @memberof Logger
  212. */
  213. _setupFunctions() {
  214. let self = this;
  215. let funcs = ['log', 'info', 'error', 'debug', 'warn'];
  216.  
  217. for (let i = 0; i < funcs.length; i++) {
  218. let func = funcs[i];
  219.  
  220. self[func] = function () {
  221. let args = [...arguments];
  222.  
  223. if (self.devTag) args.unshift(self.label);
  224.  
  225. self.window.console.debug.bind(self.window.console, ...args);
  226. };
  227. }
  228. }
  229. }
  230.  
  231. class Base64 {
  232. static keyStr = 'ABCDEFGHIJKLMNOP' + 'QRSTUVWXYZabcdef' + 'ghijklmnopqrstuv' + 'wxyz0123456789+/' + '=';
  233.  
  234. /**
  235. *
  236. *
  237. * @static
  238. * @param {string} input
  239. * @returns {string}
  240. * @memberof Base64
  241. */
  242. static encode(input) {
  243. input = escape(input);
  244.  
  245. let output = '';
  246.  
  247. let chr1;
  248. let chr2;
  249. let chr3 = '';
  250.  
  251. let enc1;
  252. let enc2;
  253. let enc3;
  254. let enc4 = '';
  255.  
  256. let i = 0;
  257.  
  258. do {
  259. chr1 = input.charCodeAt(i++);
  260. chr2 = input.charCodeAt(i++);
  261. chr3 = input.charCodeAt(i++);
  262. enc1 = chr1 >> 2;
  263. enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
  264. enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
  265. enc4 = chr3 & 63;
  266.  
  267. if (isNaN(chr2)) {
  268. enc3 = enc4 = 64;
  269. } else if (isNaN(chr3)) {
  270. enc4 = 64;
  271. }
  272.  
  273. output = output + Base64.keyStr.charAt(enc1) + Base64.keyStr.charAt(enc2) + Base64.keyStr.charAt(enc3) + Base64.keyStr.charAt(enc4);
  274. chr1 = chr2 = chr3 = '';
  275. enc1 = enc2 = enc3 = enc4 = '';
  276. } while (i < input.length);
  277.  
  278. return output;
  279. }
  280.  
  281. /**
  282. *
  283. *
  284. * @static
  285. * @param {string} input
  286. * @returns {string}
  287. * @memberof Base64
  288. */
  289. static decode(input) {
  290. let output = '';
  291.  
  292. let chr1;
  293. let chr2;
  294. let chr3 = '';
  295.  
  296. let enc1;
  297. let enc2;
  298. let enc3;
  299. let enc4 = '';
  300.  
  301. let i = 0;
  302.  
  303. let base64test = /[^A-Za-z0-9\+\/\=]/g;
  304.  
  305. if (base64test.exec(input)) {
  306. throw new Error(`There were invalid base64 characters in the input text. Valid base64 characters are: ['A-Z', 'a-z', '0-9,' '+', '/', '=']`);
  307. }
  308.  
  309. input = input.replace(/[^A-Za-z0-9\+\/\=]/g, '');
  310.  
  311. do {
  312. enc1 = Base64.keyStr.indexOf(input.charAt(i++));
  313. enc2 = Base64.keyStr.indexOf(input.charAt(i++));
  314. enc3 = Base64.keyStr.indexOf(input.charAt(i++));
  315. enc4 = Base64.keyStr.indexOf(input.charAt(i++));
  316. chr1 = (enc1 << 2) | (enc2 >> 4);
  317. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  318. chr3 = ((enc3 & 3) << 6) | enc4;
  319. output = output + String.fromCharCode(chr1);
  320.  
  321. if (enc3 != 64) output = output + String.fromCharCode(chr2);
  322.  
  323. if (enc4 != 64) output = output + String.fromCharCode(chr3);
  324.  
  325. chr1 = chr2 = chr3 = '';
  326. enc1 = enc2 = enc3 = enc4 = '';
  327. } while (i < input.length);
  328.  
  329. return unescape(output);
  330. }
  331. }
  332.  
  333. class MultiRegExp {
  334. constructor(baseRegExp) {
  335. const { regexp, groupIndexMapper, previousGroupsForGroup } = this._fillGroups(baseRegExp);
  336.  
  337. this.regexp = regexp;
  338. this.groupIndexMapper = groupIndexMapper;
  339. this.previousGroupsForGroup = previousGroupsForGroup;
  340. }
  341.  
  342. execForAllGroups(str, includeFullMatch) {
  343. let matches = RegExp.prototype.exec.call(this.regexp, str);
  344.  
  345. if (!matches) return matches;
  346.  
  347. let firstIndex = matches.index;
  348. let indexMapper = includeFullMatch
  349. ? Object.assign(
  350. {
  351. 0: 0,
  352. },
  353. this.groupIndexMapper
  354. )
  355. : this.groupIndexMapper;
  356. let previousGroups = includeFullMatch
  357. ? Object.assign(
  358. {
  359. 0: [],
  360. },
  361. this.previousGroupsForGroup
  362. )
  363. : this.previousGroupsForGroup;
  364.  
  365. let res = Object.keys(indexMapper).map((group) => {
  366. let mapped = indexMapper[group];
  367. let match = matches[mapped];
  368. let start = firstIndex + previousGroups[group].reduce((sum, i) => sum + (matches[i] ? matches[i].length : 0), 0);
  369. let end = start + (matches[mapped] ? matches[mapped].length : 0);
  370. let lineColumnStart = LineColumnFinder(str).fromIndex(start);
  371. let lineColumnEnd = LineColumnFinder(str).fromIndex(end - 1);
  372.  
  373. return {
  374. match,
  375. start,
  376. end,
  377. startLineNumber: lineColumnStart.line,
  378. startColumnNumber: lineColumnStart.col,
  379. endLineNumber: lineColumnEnd.line,
  380. endColumnNumber: lineColumnEnd.col,
  381. };
  382. });
  383.  
  384. return res;
  385. }
  386.  
  387. execForGroup(string, group) {
  388. const matches = RegExp.prototype.exec.call(this.regexp, string);
  389.  
  390. if (!matches) return matches;
  391.  
  392. const firstIndex = matches.index;
  393.  
  394. const mapped = group == 0 ? 0 : this.groupIndexMapper[group];
  395. const previousGroups = group == 0 ? [] : this.previousGroupsForGroup[group];
  396.  
  397. let r = {
  398. match: matches[mapped],
  399. start: firstIndex + previousGroups.reduce((sum, i) => sum + (matches[i] ? matches[i].length : 0), 0),
  400. };
  401.  
  402. r.end = r.start + (matches[mapped] ? matches[mapped].length : 0);
  403.  
  404. return r;
  405. }
  406.  
  407. /**
  408. * Adds brackets before and after a part of string
  409. * @param str string the hole regex string
  410. * @param start int marks the position where ( should be inserted
  411. * @param end int marks the position where ) should be inserted
  412. * @param groupsAdded int defines the offset to the original string because of inserted brackets
  413. * @return {string}
  414. */
  415. _addGroupToRegexString(str, start, end, groupsAdded) {
  416. start += groupsAdded * 2;
  417. end += groupsAdded * 2;
  418.  
  419. return str.substring(0, start) + '(' + str.substring(start, end + 1) + ')' + str.substring(end + 1);
  420. }
  421.  
  422. /**
  423. * converts the given regex to a regex where all not captured string are going to be captured
  424. * it along sides generates a mapper which maps the original group index to the shifted group offset and
  425. * generates a list of groups indexes (including new generated capturing groups)
  426. * which have been closed before a given group index (unshifted)
  427. *
  428. * Example:
  429. * regexp: /a(?: )bc(def(ghi)xyz)/g => /(a(?: )bc)((def)(ghi)(xyz))/g
  430. * groupIndexMapper: {'1': 2, '2', 4}
  431. * previousGroupsForGroup: {'1': [1], '2': [1, 3]}
  432. *
  433. * @param regex RegExp
  434. * @return {{regexp: RegExp, groupIndexMapper: {}, previousGroupsForGroup: {}}}
  435. */
  436. _fillGroups(regex) {
  437. let regexString;
  438. let modifier;
  439.  
  440. if (regex.source && regex.flags) {
  441. regexString = regex.source;
  442. modifier = regex.flags;
  443. } else {
  444. regexString = regex.toString();
  445. modifier = regexString.substring(regexString.lastIndexOf(regexString[0]) + 1); // sometimes order matters ;)
  446. regexString = regexString.substr(1, regex.toString().lastIndexOf(regexString[0]) - 1);
  447. }
  448.  
  449. // regexp is greedy so it should match (? before ( right?
  450. // brackets may be not quoted by \
  451. // closing bracket may look like: ), )+, )+?, ){1,}?, ){1,1111}?
  452. const tester = /(\\\()|(\\\))|(\(\?)|(\()|(\)(?:\{\d+,?\d*}|[*+?])?\??)/g;
  453.  
  454. let modifiedRegex = regexString;
  455.  
  456. let lastGroupStartPosition = -1;
  457. let lastGroupEndPosition = -1;
  458. let lastNonGroupStartPosition = -1;
  459. let lastNonGroupEndPosition = -1;
  460. let groupsAdded = 0;
  461. let groupCount = 0;
  462. let matchArr;
  463. const nonGroupPositions = [];
  464. const groupPositions = [];
  465. const groupNumber = [];
  466. let currentLengthIndexes = [];
  467. const groupIndexMapper = {};
  468. const previousGroupsForGroup = {};
  469.  
  470. while ((matchArr = tester.exec(regexString)) !== null) {
  471. if (matchArr[1] || matchArr[2]) {
  472. // ignore escaped brackets \(, \)
  473. }
  474.  
  475. if (matchArr[3]) {
  476. // non capturing group (?
  477. let index = matchArr.index + matchArr[0].length - 1;
  478.  
  479. lastNonGroupStartPosition = index;
  480. nonGroupPositions.push(index);
  481. } else if (matchArr[4]) {
  482. // capturing group (
  483. let index = matchArr.index + matchArr[0].length - 1;
  484. let lastGroupPosition = Math.max(lastGroupStartPosition, lastGroupEndPosition);
  485.  
  486. // if a (? is found add ) before it
  487. if (lastNonGroupStartPosition > lastGroupPosition) {
  488. // check if between ) of capturing group lies a non capturing group
  489. if (lastGroupPosition < lastNonGroupEndPosition) {
  490. // add groups for x1 and x2 on (?:()x1)x2(?:...
  491. if (lastNonGroupEndPosition - 1 - (lastGroupPosition + 1) > 0) {
  492. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastGroupPosition + 1, lastNonGroupEndPosition - 1, groupsAdded);
  493. groupsAdded++;
  494. lastGroupEndPosition = lastNonGroupEndPosition - 1; // imaginary position as it is not in regex but modifiedRegex
  495. currentLengthIndexes.push(groupCount + groupsAdded);
  496. }
  497.  
  498. if (lastNonGroupStartPosition - 1 - (lastNonGroupEndPosition + 1) > 0) {
  499. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastNonGroupEndPosition + 1, lastNonGroupStartPosition - 2, groupsAdded);
  500. groupsAdded++;
  501. lastGroupEndPosition = lastNonGroupStartPosition - 1; // imaginary position as it is not in regex but modifiedRegex
  502. currentLengthIndexes.push(groupCount + groupsAdded);
  503. }
  504. } else {
  505. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastGroupPosition + 1, lastNonGroupStartPosition - 2, groupsAdded);
  506. groupsAdded++;
  507. lastGroupEndPosition = lastNonGroupStartPosition - 1; // imaginary position as it is not in regex but modifiedRegex
  508. currentLengthIndexes.push(groupCount + groupsAdded);
  509. }
  510.  
  511. // if necessary also add group between (? and opening bracket
  512. if (index > lastNonGroupStartPosition + 2) {
  513. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastNonGroupStartPosition + 2, index - 1, groupsAdded);
  514. groupsAdded++;
  515. lastGroupEndPosition = index - 1; // imaginary position as it is not in regex but modifiedRegex
  516. currentLengthIndexes.push(groupCount + groupsAdded);
  517. }
  518. } else if (lastGroupPosition < index - 1) {
  519. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastGroupPosition + 1, index - 1, groupsAdded);
  520. groupsAdded++;
  521. lastGroupEndPosition = index - 1; // imaginary position as it is not in regex but modifiedRegex
  522. currentLengthIndexes.push(groupCount + groupsAdded);
  523. }
  524.  
  525. groupCount++;
  526. lastGroupStartPosition = index;
  527. groupPositions.push(index);
  528. groupNumber.push(groupCount + groupsAdded);
  529. groupIndexMapper[groupCount] = groupCount + groupsAdded;
  530. previousGroupsForGroup[groupCount] = currentLengthIndexes.slice();
  531. } else if (matchArr[5]) {
  532. // closing bracket ), )+, )+?, ){1,}?, ){1,1111}?
  533. let index = matchArr.index + matchArr[0].length - 1;
  534.  
  535. if ((groupPositions.length && !nonGroupPositions.length) || groupPositions[groupPositions.length - 1] > nonGroupPositions[nonGroupPositions.length - 1]) {
  536. if (lastGroupStartPosition < lastGroupEndPosition && lastGroupEndPosition < index - 1) {
  537. modifiedRegex = this._addGroupToRegexString(modifiedRegex, lastGroupEndPosition + 1, index - 1, groupsAdded);
  538. groupsAdded++;
  539.  
  540. //lastGroupEndPosition = index - 1; will be set anyway
  541. currentLengthIndexes.push(groupCount + groupsAdded);
  542. }
  543.  
  544. groupPositions.pop();
  545. lastGroupEndPosition = index;
  546.  
  547. let toPush = groupNumber.pop();
  548. currentLengthIndexes.push(toPush);
  549. currentLengthIndexes = currentLengthIndexes.filter((index) => index <= toPush);
  550. } else if (nonGroupPositions.length) {
  551. nonGroupPositions.pop();
  552. lastNonGroupEndPosition = index;
  553. }
  554. }
  555. }
  556.  
  557. return {
  558. regexp: new RegExp(modifiedRegex, modifier),
  559. groupIndexMapper,
  560. previousGroupsForGroup,
  561. };
  562. }
  563. }
  564.  
  565. class MoveableElement {
  566. /**
  567. * Creates an instance of MoveableElement.
  568. * @param {HTMLElement} element
  569. * @param {boolean} requireKeyDown
  570. * @memberof MoveableElement
  571. */
  572. constructor(element, requireKeyDown) {
  573. this.element = element;
  574. this.requireKeyDown = requireKeyDown || false;
  575. this.handleMouseDown = this.handleMouseDown.bind(this);
  576. this.handleMouseUp = this.handleMouseUp.bind(this);
  577. this.handleMouseMove = this.handleMouseMove.bind(this);
  578.  
  579. this.moving = false;
  580. this.keyPressed = false;
  581. this.originalCursor = getStyle(this.element, 'cursor');
  582.  
  583. this.setupEvents();
  584. }
  585.  
  586. setupEvents() {
  587. if (!document.body) {
  588. setTimeout(() => {
  589. this.setupEvents();
  590. }, 250);
  591. } else {
  592. document.body.addEventListener('keydown', (ev) => {
  593. if (ev.which == '17') {
  594. this.keyPressed = true;
  595. }
  596. });
  597.  
  598. document.body.addEventListener('keyup', (ev) => {
  599. this.keyPressed = false;
  600. });
  601. }
  602. }
  603.  
  604. /**
  605. *
  606. *
  607. * @author Michael Barros <michaelcbarros@gmail.com>
  608. * @param {MouseEvent} ev
  609. * @memberof MoveableElement
  610. */
  611. handleMouseDown(ev) {
  612. if (this.keyPressed || !this.requireKeyDown) {
  613. ev.preventDefault();
  614.  
  615. this.element.style.cursor = 'move';
  616.  
  617. this.changePointerEvents('none');
  618.  
  619. document.body.removeEventListener('mouseup', this.handleMouseUp);
  620. document.body.addEventListener('mouseup', this.handleMouseUp);
  621.  
  622. document.body.removeEventListener('mousemove', this.handleMouseMove);
  623. document.body.removeEventListener('mouseleave', this.handleMouseUp);
  624.  
  625. document.body.addEventListener('mousemove', this.handleMouseMove);
  626. document.body.addEventListener('mouseleave', this.handleMouseUp);
  627.  
  628. try {
  629. document.querySelectorAll('iframe')[0].style.pointerEvents = 'none';
  630. } catch (error) {}
  631. }
  632. }
  633.  
  634. changePointerEvents(value) {
  635. for (let i = 0; i < this.element.children.length; i++) {
  636. const child = this.element.children[i];
  637.  
  638. child.style.pointerEvents = value;
  639. }
  640. }
  641.  
  642. /**
  643. *
  644. *
  645. * @author Michael Barros <michaelcbarros@gmail.com>
  646. * @param {MouseEvent} ev
  647. * @memberof MoveableElement
  648. */
  649. handleMouseUp(ev) {
  650. this.moving = false;
  651. this.element.style.cursor = this.originalCursor;
  652. this.changePointerEvents('auto');
  653.  
  654. document.body.removeEventListener('mouseup', this.handleMouseUp);
  655. document.body.removeEventListener('mousemove', this.handleMouseMove);
  656. document.body.removeEventListener('mouseleave', this.handleMouseUp);
  657.  
  658. try {
  659. document.querySelectorAll('iframe')[0].style.pointerEvents = '';
  660. } catch (error) {}
  661. }
  662.  
  663. /**
  664. *
  665. *
  666. * @author Michael Barros <michaelcbarros@gmail.com>
  667. * @param {MouseEvent} ev
  668. * @memberof MoveableElement
  669. */
  670. handleMouseMove(ev) {
  671. this.moving = true;
  672.  
  673. let top = ev.clientY - getStyle(this.element, 'height') / 2;
  674. let bottom = ev.clientX - getStyle(this.element, 'width') / 2;
  675.  
  676. this.element.style.top = `${top}px`;
  677. this.element.style.left = `${bottom}px`;
  678. }
  679.  
  680. padCoord(coord) {
  681. return coord.toString().padStart(5, ' ');
  682. }
  683.  
  684. init() {
  685. this.element.addEventListener('mousedown', this.handleMouseDown);
  686. }
  687. }
  688.  
  689. class CurrentLine {
  690. /**
  691. * @typedef ILineInfo
  692. * @prop {string} method
  693. * @prop {number} line
  694. * @prop {string} file
  695. * @prop {string} filename
  696. */
  697.  
  698. /**
  699. * Returns a single item
  700. *
  701. * @param {number} [level] Useful to return levels up on the stack. If not informed, the first (0, zero index) element of the stack will be returned
  702. * @returns {ILineInfo}
  703. */
  704. get(level = 0) {
  705. const stack = getStack();
  706. const i = Math.min(level + 1, stack.length - 1);
  707. const item = stack[i];
  708. const result = CurrentLine.parse(item);
  709.  
  710. return result;
  711. }
  712.  
  713. /**
  714. * Returns all stack
  715. *
  716. * @returns {ILineInfo[]}
  717. */
  718. all() {
  719. const stack = getStack();
  720. const result = [];
  721.  
  722. for (let i = 1; i < stack.length; i++) {
  723. const item = stack[i];
  724.  
  725. result.push(CurrentLine.parse(item));
  726. }
  727.  
  728. return result;
  729. }
  730.  
  731. /**
  732. *
  733. *
  734. * @param {NodeJS.CallSite} item
  735. * @returns {ILineInfo}
  736. */
  737. static parse(item) {
  738. const result = {
  739. method: item.getMethodName() || item.getFunctionName(),
  740. line: item.getLineNumber(),
  741. file: item.getFileName() || item.getScriptNameOrSourceURL(),
  742. };
  743.  
  744. result.filename = result.file ? result.file.replace(/^.*\/|\\/gm, '').replace(/\.\w+$/gm, '') : null;
  745.  
  746. return result;
  747. }
  748. }
  749.  
  750. /**
  751. *
  752. *
  753. * @returns {NodeJS.CallSite[]}
  754. */
  755. function getStack() {
  756. const orig = Error.prepareStackTrace;
  757.  
  758. Error.prepareStackTrace = function (_, stack) {
  759. return stack;
  760. };
  761.  
  762. const err = new Error();
  763.  
  764. Error.captureStackTrace(err, arguments.callee);
  765.  
  766. const stack = err.stack;
  767.  
  768. Error.prepareStackTrace = orig;
  769.  
  770. return stack;
  771. }
  772.  
  773. class ProgressTimer {
  774. /**
  775. * Creates an instance of ProgressTimer.
  776. * @param {number} total
  777. * @memberof ProgressTimer
  778. */
  779. constructor(total) {
  780. this.startTime;
  781. this.total = total;
  782. this.loaded = 0;
  783. this.estimatedFinishDt = '';
  784. this.progressMessage = '';
  785. }
  786.  
  787. /**
  788. *
  789. *
  790. * @memberof ProgressTimer
  791. */
  792. start() {
  793. this.startTime = new Date();
  794. }
  795.  
  796. /**
  797. *
  798. *
  799. * @param {number} loaded
  800. * @param {string} msg
  801. * @memberof ProgressTimer
  802. */
  803. updateProgress(loaded, msg) {
  804. this.loaded = loaded;
  805.  
  806. this.progress = `${((this.loaded * 100) / this.total).toFixed(2)}%`;
  807. this.timeRemaining = this._estimatedTimeRemaining(this.startTime, this.loaded, this.total);
  808. this.downloaded = `${this.loaded}/${this.total}`;
  809. this.completionTime = `${this._dateToISOLikeButLocal(this.estimatedFinishDt)}`;
  810. this.totalRuntime = `${this._ms2Timestamp(this.timeTaken)}`;
  811.  
  812. this.updateProgressMessage(msg);
  813. this.printProgress();
  814. }
  815.  
  816. /**
  817. *
  818. *
  819. * @param {string} msg
  820. * @memberof ProgressTimer
  821. */
  822. updateProgressMessage(msg) {
  823. let msgLines = [];
  824.  
  825. msgLines.push(` completed: ${this.progress}`);
  826. msgLines.push(` downloaded: ${this.downloaded}`);
  827. msgLines.push(` total runtime: ${this.totalRuntime}`);
  828. msgLines.push(` time remaining: ${this.timeRemaining}`);
  829. msgLines.push(`completion time: ${this.completionTime}`);
  830.  
  831. if (msg) {
  832. msgLines.push(msg);
  833. }
  834.  
  835. this.progressMessage = msgLines.join('\n');
  836. }
  837.  
  838. /**
  839. *
  840. *
  841. * @memberof ProgressTimer
  842. */
  843. printProgress() {
  844. console.clear();
  845. console.debug(this.progressMessage);
  846. }
  847.  
  848. /**
  849. *
  850. *
  851. * @param {Date} startTime
  852. * @param {number} itemsProcessed
  853. * @param {number} totalItems
  854. * @returns {string}
  855. * @memberof ProgressTimer
  856. */
  857. _estimatedTimeRemaining(startTime, itemsProcessed, totalItems) {
  858. // if (itemsProcessed == 0) {
  859. // return '';
  860. // }
  861.  
  862. let currentTime = new Date();
  863. this.timeTaken = currentTime - startTime;
  864. this.timeLeft = itemsProcessed == 0 ? this.timeTaken * (totalItems - itemsProcessed) : (this.timeTaken / itemsProcessed) * (totalItems - itemsProcessed);
  865. this.estimatedFinishDt = new Date(currentTime.getTime() + this.timeLeft);
  866.  
  867. return this._ms2Timestamp(this.timeLeft);
  868. }
  869.  
  870. /**
  871. *
  872. *
  873. * @param {number} ms
  874. * @returns {string}
  875. * @memberof ProgressTimer
  876. */
  877. _ms2Timestamp(ms) {
  878. // 1- Convert to seconds:
  879. let seconds = ms / 1000;
  880.  
  881. // 2- Extract hours:
  882. let hours = parseInt(seconds / 3600); // 3,600 seconds in 1 hour
  883. seconds = seconds % 3600; // seconds remaining after extracting hours
  884.  
  885. // 3- Extract minutes:
  886. let minutes = parseInt(seconds / 60); // 60 seconds in 1 minute
  887.  
  888. // 4- Keep only seconds not extracted to minutes:
  889. seconds = seconds % 60;
  890.  
  891. let parts = seconds.toString().split('.');
  892.  
  893. seconds = parseInt(parts[0]);
  894. let milliseconds = parts.length > 1 ? parts[1].substring(0, 3).padEnd(3, 0) : '000';
  895.  
  896. hours = hours.toString().padStart(2, '0');
  897. minutes = minutes.toString().padStart(2, '0');
  898. seconds = seconds.toString().padStart(2, '0');
  899.  
  900. return `${hours}:${minutes}:${seconds}.${milliseconds}`; // hours + ':' + minutes + ':' + seconds;
  901. }
  902.  
  903. /**
  904. *
  905. *
  906. * @author Michael Barros <michaelcbarros@gmail.com>
  907. * @param {Date} date
  908. * @returns {string}
  909. * @memberof ProgressTimer
  910. */
  911. _dateToISOLikeButLocal(date) {
  912. let offsetMs = date.getTimezoneOffset() * 60 * 1000;
  913. let msLocal = date.getTime() - offsetMs;
  914. let dateLocal = new Date(msLocal);
  915. let iso = dateLocal.toISOString();
  916. let isoLocal = iso.slice(0, 19);
  917.  
  918. return isoLocal.replace(/T/g, ' ');
  919. }
  920. }
  921.  
  922. class Benchmark {
  923. constructor({ logger, printResults } = {}) {
  924. this.namedPerformances = {};
  925. this.defaultName = 'default';
  926. this.logger = logger;
  927. this.printResults = printResults == undefined ? (this.logger ? true : false) : printResults;
  928. }
  929.  
  930. /**
  931. *
  932. *
  933. * @author Michael Barros <michaelcbarros@gmail.com>
  934. * @param {string=} name
  935. * @memberof Benchmark
  936. */
  937. start(name) {
  938. name = name || this.defaultName;
  939.  
  940. this.namedPerformances[name] = {
  941. startAt: this._hrtime(),
  942. };
  943. }
  944.  
  945. /**
  946. *
  947. *
  948. * @author Michael Barros <michaelcbarros@gmail.com>
  949. * @param {string=} name
  950. * @memberof Benchmark
  951. */
  952. stop(name) {
  953. name = name || this.defaultName;
  954.  
  955. const startAt = this.namedPerformances[name] && this.namedPerformances[name].startAt;
  956.  
  957. if (!startAt) throw new Error(`Namespace: ${name} doesnt exist`);
  958.  
  959. const diff = this._hrtime(startAt);
  960. const time = diff[0] * 1e3 + diff[1] * 1e-6;
  961. const words = this.getWords(diff);
  962. const preciseWords = this.getPreciseWords(diff);
  963. const verboseWords = this.getVerboseWords(diff);
  964. const verboseAbbrWords = this.getVerboseAbbrWords(diff);
  965.  
  966. if (this.printResults) {
  967. let output = name != 'default' ? `[${name}] execution time:` : `execution time:`;
  968.  
  969. this.logger(output, time); // words
  970. }
  971.  
  972. return {
  973. name,
  974. time,
  975. words,
  976. preciseWords,
  977. verboseWords,
  978. verboseAbbrWords,
  979. diff,
  980. };
  981. }
  982.  
  983. /**
  984. *
  985. *
  986. * @author Michael Barros <michaelcbarros@gmail.com>
  987. * @param {T} func
  988. * @param {{name: string, measure: boolean}=} { name, measure }
  989. * @returns {T}
  990. * @memberof Benchmark
  991. * @template T
  992. */
  993. wrapFunc(func, { name, measure = true } = {}) {
  994. name = this._getFuncName(func, name);
  995.  
  996. let self = this;
  997.  
  998. wrappedFunc.measure = measure;
  999. wrappedFunc.benchmark = {
  1000. name,
  1001. results: {
  1002. runs: [],
  1003. avg: null,
  1004. min: null,
  1005. max: null,
  1006. total: null,
  1007. times: null,
  1008. runCount: 0,
  1009. },
  1010. reset: function () {
  1011. this.results.runs = [];
  1012. this.results.avg = null;
  1013. this.results.min = null;
  1014. this.results.max = null;
  1015. this.results.total = null;
  1016. this.results.times = null;
  1017. this.results.runCount = 0;
  1018. },
  1019. printResults: function (logger = console.debug) {
  1020. let output = this.name != 'default' ? `[${this.name}] execution summary:` : `execution summary:`;
  1021.  
  1022. logger(output, `times: ${this.results.runCount} total: ${self.getWords(this.results.total)} min: ${self.getWords(this.results.min)} max: ${self.getWords(this.results.max)} avg: ${self.getWords(this.results.avg)} total: ${self.getWords(this.results.total)}`);
  1023. },
  1024. };
  1025.  
  1026. function wrappedFunc() {
  1027. if (wrappedFunc.measure) {
  1028. self.start(name);
  1029. }
  1030.  
  1031. let res = func(...arguments);
  1032.  
  1033. if (wrappedFunc.measure) {
  1034. wrappedFunc.benchmark.results.runCount++;
  1035.  
  1036. wrappedFunc.benchmark.results.runs.push(self.stop(name));
  1037.  
  1038. let times = wrappedFunc.benchmark.results.runs.map((run) => {
  1039. return run.time;
  1040. });
  1041.  
  1042. wrappedFunc.benchmark.results.times = times;
  1043. wrappedFunc.benchmark.results.avg = self._getAvgTime(times);
  1044. wrappedFunc.benchmark.results.total = self._getSumTime(times);
  1045. wrappedFunc.benchmark.results.min = Math.min(...times);
  1046. wrappedFunc.benchmark.results.max = Math.max(...times);
  1047. }
  1048.  
  1049. return res;
  1050. }
  1051.  
  1052. return this._defineWrappedFuncProperties(wrappedFunc, name);
  1053. }
  1054.  
  1055. /**
  1056. *
  1057. *
  1058. * @author Michael Barros <michaelcbarros@gmail.com>
  1059. * @param {T} func
  1060. * @param {{name: string, measure: boolean}=} { name, measure }
  1061. * @returns {T}
  1062. * @memberof Benchmark
  1063. * @template T
  1064. */
  1065. wrapAsyncFunc(func, { name, measure = true } = {}) {
  1066. name = this._getFuncName(func, name);
  1067.  
  1068. let self = this;
  1069.  
  1070. async function wrappedFunc() {
  1071. if (wrappedFunc.measure) self.start(name);
  1072.  
  1073. let res = await func(...arguments);
  1074.  
  1075. if (wrappedFunc.measure) self.stop(name);
  1076.  
  1077. return res;
  1078. }
  1079.  
  1080. wrappedFunc.measure = measure;
  1081.  
  1082. return this._defineWrappedFuncProperties(wrappedFunc, name);
  1083. }
  1084.  
  1085. getWords(diff) {
  1086. return this._prettyHrtime(diff);
  1087. }
  1088.  
  1089. getPreciseWords(diff) {
  1090. return this._prettyHrtime(diff, { precise: true });
  1091. }
  1092.  
  1093. getVerboseWords(diff) {
  1094. return this._prettyHrtime(diff, { verbose: true });
  1095. }
  1096.  
  1097. getVerboseAbbrWords(diff) {
  1098. return this._prettyHrtime(diff, { verbose: true, verboseAbbrv: true, precise: true });
  1099. }
  1100.  
  1101. /**
  1102. *
  1103. *
  1104. * @author Michael Barros <michaelcbarros@gmail.com>
  1105. * @param {number[][]} times
  1106. * @returns {number}
  1107. */
  1108. _getAvgTime(times) {
  1109. return this._getSumTime(times) / times.length;
  1110. }
  1111.  
  1112. /**
  1113. *
  1114. *
  1115. * @author Michael Barros <michaelcbarros@gmail.com>
  1116. * @param {number[][]} times
  1117. * @returns {number}
  1118. */
  1119. _getSumTime(times) {
  1120. return times.reduce((a, b) => a + b);
  1121. }
  1122.  
  1123. /**
  1124. *
  1125. *
  1126. * @author Michael Barros <michaelcbarros@gmail.com>
  1127. * @param {number} ms
  1128. * @returns {number[][]}
  1129. * @memberof Benchmark
  1130. */
  1131. _ms2Hrtime(ms) {
  1132. let seconds = Math.round(ms / 1000);
  1133. let nanoSeconds = Math.round(ms * 1000000 - seconds * 1000000 * 1000);
  1134.  
  1135. return [seconds, nanoSeconds];
  1136. }
  1137.  
  1138. /**
  1139. *
  1140. *
  1141. * @author Michael Barros <michaelcbarros@gmail.com>
  1142. * @param {T} func
  1143. * @param {string=} name
  1144. * @returns {T}
  1145. * @memberof Benchmark
  1146. * @template T
  1147. */
  1148. _getFuncName(func, name) {
  1149. return name ? name : 'name' in func && func.name.trim() !== '' ? func.name : '[wrapped.func]';
  1150. }
  1151.  
  1152. /**
  1153. *
  1154. *
  1155. * @author Michael Barros <michaelcbarros@gmail.com>
  1156. * @param {Function} wrappedFunc
  1157. * @param {string} name
  1158. * @returns {Function}
  1159. * @memberof Benchmark
  1160. */
  1161. _defineWrappedFuncProperties(wrappedFunc, name) {
  1162. Object.defineProperty(wrappedFunc, 'name', {
  1163. value: name,
  1164. writable: false,
  1165. configurable: false,
  1166. enumerable: false,
  1167. });
  1168.  
  1169. Object.defineProperty(wrappedFunc, 'toString', {
  1170. value: () => func.toString(),
  1171. writable: false,
  1172. configurable: false,
  1173. enumerable: false,
  1174. });
  1175.  
  1176. return wrappedFunc;
  1177. }
  1178.  
  1179. /**
  1180. *
  1181. *
  1182. * @author Michael Barros <michaelcbarros@gmail.com>
  1183. * @param {[number, number]=} time
  1184. * @returns {[number, number]}
  1185. * @memberof Benchmark
  1186. */
  1187. _hrtime(time) {
  1188. if (typeof process !== 'undefined') return process.hrtime(time);
  1189.  
  1190. var performance = typeof performance !== 'undefined' ? performance : {};
  1191. let performanceNow = performance.now || performance.mozNow || performance.msNow || performance.oNow || performance.webkitNow || (() => new Date().getTime());
  1192.  
  1193. let clocktime = performanceNow.call(performance) * 1e-3;
  1194. let seconds = Math.floor(clocktime);
  1195. let nanoseconds = Math.floor((clocktime % 1) * 1e9);
  1196.  
  1197. if (time) {
  1198. seconds = seconds - time[0];
  1199. nanoseconds = nanoseconds - time[1];
  1200.  
  1201. if (nanoseconds < 0) {
  1202. seconds--;
  1203. nanoseconds += 1e9;
  1204. }
  1205. }
  1206.  
  1207. return [seconds, nanoseconds];
  1208. }
  1209.  
  1210. /**
  1211. *
  1212. *
  1213. * @author Michael Barros <michaelcbarros@gmail.com>
  1214. * @param {[number, number]=} time
  1215. * @param {{verbose: boolean; verboseAbbrv: boolean; precise: boolean}} { verbose = false, verboseAbbrv = false, precise = false }
  1216. * @returns {string}
  1217. * @memberof Benchmark
  1218. */
  1219. _prettyHrtime(time, { verbose = false, verboseAbbrv = false, precise = false } = {}) {
  1220. let i, spot, sourceAtStep, valAtStep, decimals, strAtStep, results, totalSeconds;
  1221.  
  1222. let minimalDesc = ['h', 'min', 's', 'ms', 'μs', 'ns'];
  1223. let verboseDesc = !verboseAbbrv ? ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'nanosecond'] : minimalDesc;
  1224. let convert = [60 * 60, 60, 1, 1e6, 1e3, 1];
  1225.  
  1226. if (typeof time === 'number') {
  1227. time = this._ms2Hrtime(time);
  1228. }
  1229.  
  1230. if (!Array.isArray(time) || time.length !== 2) return '';
  1231.  
  1232. if (typeof time[0] !== 'number' || typeof time[1] !== 'number') return '';
  1233.  
  1234. // normalize source array due to changes in node v5.4+
  1235. if (time[1] < 0) {
  1236. totalSeconds = time[0] + time[1] / 1e9;
  1237. time[0] = parseInt(totalSeconds);
  1238. time[1] = parseFloat((totalSeconds % 1).toPrecision(9)) * 1e9;
  1239. }
  1240.  
  1241. results = '';
  1242.  
  1243. for (i = 0; i < 6; i++) {
  1244. // grabbing first or second spot in source array
  1245. spot = i < 3 ? 0 : 1;
  1246. sourceAtStep = time[spot];
  1247.  
  1248. if (i !== 3 && i !== 0) {
  1249. // trim off previous portions
  1250. sourceAtStep = sourceAtStep % convert[i - 1];
  1251. }
  1252.  
  1253. if (i === 2) {
  1254. // get partial seconds from other portion of the array
  1255. sourceAtStep += time[1] / 1e9;
  1256. }
  1257.  
  1258. // val at this unit
  1259. valAtStep = sourceAtStep / convert[i];
  1260.  
  1261. if (valAtStep >= 1) {
  1262. if (verbose) {
  1263. // deal in whole units, subsequent laps will get the decimal portion
  1264. valAtStep = Math.floor(valAtStep);
  1265. }
  1266.  
  1267. if (!precise) {
  1268. // don't fling too many decimals
  1269. decimals = valAtStep >= 10 ? 0 : 2;
  1270. strAtStep = valAtStep.toFixed(decimals);
  1271. } else {
  1272. strAtStep = valAtStep.toString();
  1273. }
  1274.  
  1275. if (strAtStep.indexOf('.') > -1 && strAtStep[strAtStep.length - 1] === '0') {
  1276. // remove trailing zeros
  1277. strAtStep = strAtStep.replace(/\.?0+$/, '');
  1278. }
  1279.  
  1280. if (results) {
  1281. // append space if we have a previous value
  1282. results += ' ';
  1283. }
  1284.  
  1285. // append the value
  1286. results += strAtStep;
  1287.  
  1288. // append units
  1289. if (verbose) {
  1290. results += verboseAbbrv ? `${verboseDesc[i]}` : ` ${verboseDesc[i]}`;
  1291.  
  1292. if (!verboseAbbrv && strAtStep !== '1') {
  1293. results += 's';
  1294. }
  1295. } else {
  1296. results += ` ${minimalDesc[i]}`;
  1297. }
  1298.  
  1299. if (!verbose) {
  1300. // verbose gets as many groups as necessary, the rest get only one
  1301. break;
  1302. }
  1303. }
  1304. }
  1305.  
  1306. return results;
  1307. }
  1308. }
  1309.  
  1310. class ArrayStat {
  1311. /**
  1312. * Creates an instance of ArrayStat.
  1313. * @author Michael Barros <michaelcbarros@gmail.com>
  1314. * @param {number[]} array
  1315. * @memberof ArrayStat
  1316. */
  1317. constructor(array) {
  1318. this.array = array;
  1319. }
  1320.  
  1321. _getCloned() {
  1322. return this.array.slice(0);
  1323. }
  1324.  
  1325. min() {
  1326. return Math.min.apply(null, this.array);
  1327. }
  1328.  
  1329. max() {
  1330. return Math.max.apply(null, this.array);
  1331. }
  1332.  
  1333. range() {
  1334. return this.max(this.array) - this.min(this.array);
  1335. }
  1336.  
  1337. midrange() {
  1338. return this.range(this.array) / 2;
  1339. }
  1340.  
  1341. sum(array) {
  1342. array = array || this.array;
  1343.  
  1344. let total = 0;
  1345.  
  1346. for (let i = 0, l = array.length; i < l; i++) total += array[i];
  1347.  
  1348. return total;
  1349. }
  1350.  
  1351. mean(array) {
  1352. array = array || this.array;
  1353.  
  1354. return this.sum(array) / array.length;
  1355. }
  1356.  
  1357. median() {
  1358. let array = this._getCloned();
  1359.  
  1360. array.sort(function (a, b) {
  1361. return a - b;
  1362. });
  1363.  
  1364. let mid = array.length / 2;
  1365.  
  1366. return mid % 1 ? array[mid - 0.5] : (array[mid - 1] + array[mid]) / 2;
  1367. }
  1368.  
  1369. modes() {
  1370. if (!this.array.length) return [];
  1371.  
  1372. let modeMap = {};
  1373. let maxCount = 0;
  1374. let modes = [];
  1375.  
  1376. this.array.forEach(function (val) {
  1377. if (!modeMap[val]) modeMap[val] = 1;
  1378. else modeMap[val]++;
  1379.  
  1380. if (modeMap[val] > maxCount) {
  1381. modes = [val];
  1382. maxCount = modeMap[val];
  1383. } else if (modeMap[val] === maxCount) {
  1384. modes.push(val);
  1385.  
  1386. maxCount = modeMap[val];
  1387. }
  1388. });
  1389.  
  1390. return modes;
  1391. }
  1392.  
  1393. letiance() {
  1394. let mean = this.mean();
  1395.  
  1396. return this.mean(
  1397. this._getCloned().map(function (num) {
  1398. return Math.pow(num - mean, 2);
  1399. })
  1400. );
  1401. }
  1402.  
  1403. standardDeviation() {
  1404. return Math.sqrt(this.letiance());
  1405. }
  1406.  
  1407. meanAbsoluteDeviation() {
  1408. let mean = this.mean();
  1409.  
  1410. return this.mean(
  1411. this._getCloned().map(function (num) {
  1412. return Math.abs(num - mean);
  1413. })
  1414. );
  1415. }
  1416.  
  1417. zScores() {
  1418. let mean = this.mean();
  1419. let standardDeviation = this.standardDeviation();
  1420.  
  1421. return this._getCloned().map(function (num) {
  1422. return (num - mean) / standardDeviation;
  1423. });
  1424. }
  1425.  
  1426. withinStd(val, stdev) {
  1427. let low = this.mean() - stdev * this.standardDeviation(); // x.deviation;
  1428. let hi = this.mean() + stdev * this.standardDeviation(); // x.deviation;
  1429. let res = val > low && val < hi;
  1430.  
  1431. console.log(`val: ${val.toString().padEnd(5, ' ')} mean: ${this.mean()} stdev: ${this.standardDeviation()} hi: ${hi} low: ${low} res: ${res}`);
  1432.  
  1433. return res;
  1434. }
  1435. }
  1436.  
  1437. memoizeClass(ArrayStat);
  1438.  
  1439. let LineColumnFinder = (function LineColumnFinder() {
  1440. let isArray = Array.isArray;
  1441. let isObject = (val) => val != null && typeof val === 'object' && Array.isArray(val) === false;
  1442. let slice = Array.prototype.slice;
  1443.  
  1444. /**
  1445. * Finder for index and line-column from given string.
  1446. *
  1447. * You can call this without `new` operator as it returns an instance anyway.
  1448. *
  1449. * @class
  1450. * @param {string} str - A string to be parsed.
  1451. * @param {Object|number} [options] - Options.
  1452. * This can be an index in the string for shorthand of `lineColumn(str, index)`.
  1453. * @param {number} [options.origin=1] - The origin value of line and column.
  1454. */
  1455. function LineColumnFinder(str, options) {
  1456. if (!(this instanceof LineColumnFinder)) {
  1457. if (typeof options === 'number') {
  1458. return new LineColumnFinder(str).fromIndex(options);
  1459. }
  1460.  
  1461. return new LineColumnFinder(str, options);
  1462. }
  1463.  
  1464. this.str = str || '';
  1465. this.lineToIndex = buildLineToIndex(this.str);
  1466.  
  1467. options = options || {};
  1468.  
  1469. this.origin = typeof options.origin === 'undefined' ? 1 : options.origin;
  1470. }
  1471.  
  1472. /**
  1473. * Find line and column from index in the string.
  1474. *
  1475. * @param {number} index - Index in the string. (0-origin)
  1476. * @return {Object|null}
  1477. * Found line number and column number in object `{ line: X, col: Y }`.
  1478. * If the given index is out of range, it returns `null`.
  1479. */
  1480. LineColumnFinder.prototype.fromIndex = function (index) {
  1481. if (index < 0 || index >= this.str.length || isNaN(index)) {
  1482. return null;
  1483. }
  1484.  
  1485. let line = findLowerIndexInRangeArray(index, this.lineToIndex);
  1486.  
  1487. return {
  1488. line: line + this.origin,
  1489. col: index - this.lineToIndex[line] + this.origin,
  1490. };
  1491. };
  1492.  
  1493. /**
  1494. * Find index from line and column in the string.
  1495. *
  1496. * @param {number|Object|Array} line - Line number in the string.
  1497. * This can be an Object of `{ line: X, col: Y }`, or
  1498. * an Array of `[line, col]`.
  1499. * @param {number} [column] - Column number in the string.
  1500. * This must be omitted or undefined when Object or Array is given
  1501. * to the first argument.
  1502. * @return {number}
  1503. * Found index in the string. (always 0-origin)
  1504. * If the given line or column is out of range, it returns `-1`.
  1505. */
  1506. LineColumnFinder.prototype.toIndex = function (line, column) {
  1507. if (typeof column === 'undefined') {
  1508. if (isArray(line) && line.length >= 2) {
  1509. return this.toIndex(line[0], line[1]);
  1510. }
  1511.  
  1512. if (isObject(line) && 'line' in line && ('col' in line || 'column' in line)) {
  1513. return this.toIndex(line.line, 'col' in line ? line.col : line.column);
  1514. }
  1515.  
  1516. return -1;
  1517. }
  1518.  
  1519. if (isNaN(line) || isNaN(column)) {
  1520. return -1;
  1521. }
  1522.  
  1523. line -= this.origin;
  1524. column -= this.origin;
  1525.  
  1526. if (line >= 0 && column >= 0 && line < this.lineToIndex.length) {
  1527. let lineIndex = this.lineToIndex[line];
  1528. let nextIndex = line === this.lineToIndex.length - 1 ? this.str.length : this.lineToIndex[line + 1];
  1529.  
  1530. if (column < nextIndex - lineIndex) {
  1531. return lineIndex + column;
  1532. }
  1533. }
  1534.  
  1535. return -1;
  1536. };
  1537.  
  1538. /**
  1539. * Build an array of indexes of each line from a string.
  1540. *
  1541. * @private
  1542. * @param str {string} An input string.
  1543. * @return {number[]} Built array of indexes. The key is line number.
  1544. */
  1545. function buildLineToIndex(str) {
  1546. let lines = str.split('\n');
  1547. let lineToIndex = new Array(lines.length);
  1548. let index = 0;
  1549.  
  1550. for (let i = 0, l = lines.length; i < l; i++) {
  1551. lineToIndex[i] = index;
  1552. index += lines[i].length + /* "\n".length */ 1;
  1553. }
  1554.  
  1555. return lineToIndex;
  1556. }
  1557.  
  1558. /**
  1559. * Find a lower-bound index of a value in a sorted array of ranges.
  1560. *
  1561. * Assume `arr = [0, 5, 10, 15, 20]` and
  1562. * this returns `1` for `value = 7` (5 <= value < 10),
  1563. * and returns `3` for `value = 18` (15 <= value < 20).
  1564. *
  1565. * @private
  1566. * @param arr {number[]} An array of values representing ranges.
  1567. * @param value {number} A value to be searched.
  1568. * @return {number} Found index. If not found `-1`.
  1569. */
  1570. function findLowerIndexInRangeArray(value, arr) {
  1571. if (value >= arr[arr.length - 1]) {
  1572. return arr.length - 1;
  1573. }
  1574.  
  1575. let min = 0,
  1576. max = arr.length - 2,
  1577. mid;
  1578.  
  1579. while (min < max) {
  1580. mid = min + ((max - min) >> 1);
  1581.  
  1582. if (value < arr[mid]) {
  1583. max = mid - 1;
  1584. } else if (value >= arr[mid + 1]) {
  1585. min = mid + 1;
  1586. } else {
  1587. // value >= arr[mid] && value < arr[mid + 1]
  1588. min = mid;
  1589. break;
  1590. }
  1591. }
  1592.  
  1593. return min;
  1594. }
  1595.  
  1596. return LineColumnFinder;
  1597. })();
  1598.  
  1599. class CustomContextMenu {
  1600. /**
  1601. * Example menuItems
  1602. *
  1603. * ```javascript
  1604. * let menuItems = [
  1605. * {
  1606. * type: 'item',
  1607. * label: 'Test1',
  1608. * onClick: () => {
  1609. * alert('test1');
  1610. * },
  1611. * },
  1612. * {
  1613. * type: 'item',
  1614. * label: 'Test2',
  1615. * onClick: () => {
  1616. * console.debug('test2');
  1617. * },
  1618. * },
  1619. * {
  1620. * type: 'break',
  1621. * },
  1622. * {
  1623. * type: 'item',
  1624. * label: 'Test3',
  1625. * onClick: () => {
  1626. * console.debug('test3');
  1627. * },
  1628. * },
  1629. * ];
  1630. * ```
  1631. * @author Michael Barros <michaelcbarros@gmail.com>
  1632. * @param {HTMLElement} elemToAttachTo
  1633. * @param {*} menuItems
  1634. * @memberof CustomContextMenu
  1635. */
  1636. constructor(elemToAttachTo, menuItems, onContextMenu) {
  1637. this.elem = elemToAttachTo;
  1638. this.menuItems = menuItems;
  1639. this.menu = null;
  1640. this.onContextMenu = onContextMenu;
  1641.  
  1642. this._createMenu();
  1643. this._setupEvents();
  1644.  
  1645. this.hide = debounce(this.hide.bind(this), 500, true);
  1646. }
  1647.  
  1648. /**
  1649. *
  1650. *
  1651. * @author Michael Barros <michaelcbarros@gmail.com>
  1652. * @param {number} top
  1653. * @param {number} left
  1654. * @memberof CustomContextMenu
  1655. */
  1656. show(top, left) {
  1657. document.body.appendChild(this.menu);
  1658.  
  1659. this.menu.style.display = 'block';
  1660.  
  1661. this.menu.style.top = `${top}px`;
  1662. this.menu.style.left = `${left}px`;
  1663.  
  1664. this.menu.setAttribute('tabindex', '0');
  1665. this.menu.focus();
  1666. }
  1667.  
  1668. hide() {
  1669. this.menu.style.display = 'none';
  1670.  
  1671. if (document.body.contains(this.menu)) {
  1672. this.menu.remove();
  1673. }
  1674. }
  1675.  
  1676. _setupEvents() {
  1677. this.elem.addEventListener('contextmenu', (ev) => {
  1678. ev.preventDefault();
  1679.  
  1680. if (this.onContextMenu) {
  1681. this.onContextMenu(ev);
  1682. }
  1683.  
  1684. this.show(ev.pageY, ev.pageX);
  1685. });
  1686.  
  1687. document.addEventListener('click', (ev) => {
  1688. if (document.body.contains(this.menu) && !this._isHover(this.menu)) {
  1689. this.hide();
  1690. }
  1691. });
  1692.  
  1693. window.addEventListener('blur', (ev) => {
  1694. this.hide();
  1695. });
  1696.  
  1697. this.menu.addEventListener('blur', (ev) => {
  1698. this.hide();
  1699. });
  1700. }
  1701.  
  1702. _createMenu() {
  1703. this.menu = this._createMenuContainer();
  1704.  
  1705. for (let i = 0; i < this.menuItems.length; i++) {
  1706. let itemConfig = this.menuItems[i];
  1707.  
  1708. switch (itemConfig.type) {
  1709. case 'item':
  1710. this.menu.appendChild(this._createItem(itemConfig));
  1711.  
  1712. break;
  1713.  
  1714. case 'break':
  1715. this.menu.appendChild(this._createBreak());
  1716.  
  1717. break;
  1718.  
  1719. default:
  1720. break;
  1721. }
  1722. }
  1723.  
  1724. // document.body.appendChild(this.menu);
  1725. }
  1726.  
  1727. /**
  1728. *
  1729. *
  1730. * @author Michael Barros <michaelcbarros@gmail.com>
  1731. * @returns {HTMLElement}
  1732. * @memberof CustomContextMenu
  1733. */
  1734. _createMenuContainer() {
  1735. let html = `<div class="context" hidden></div>`;
  1736.  
  1737. let elem = this._createElementsFromHTML(html);
  1738.  
  1739. return elem;
  1740. }
  1741.  
  1742. /**
  1743. *
  1744. *
  1745. * @author Michael Barros <michaelcbarros@gmail.com>
  1746. * @param {*} itemConfig
  1747. * @returns {HTMLElement}
  1748. * @memberof CustomContextMenu
  1749. */
  1750. _createItem(itemConfig) {
  1751. let html = `<div class="context_item">
  1752. <div class="inner_item">
  1753. ${itemConfig.label}
  1754. </div>
  1755. </div>`;
  1756.  
  1757. let elem = this._createElementsFromHTML(html);
  1758.  
  1759. if (itemConfig.id) {
  1760. elem.id = itemConfig.id;
  1761. }
  1762.  
  1763. if (itemConfig.onClick) {
  1764. elem.addEventListener('click', (ev) => {
  1765. itemConfig.onClick(ev);
  1766.  
  1767. this.hide();
  1768. });
  1769. }
  1770.  
  1771. return elem;
  1772. }
  1773.  
  1774. /**
  1775. *
  1776. *
  1777. * @author Michael Barros <michaelcbarros@gmail.com>
  1778. * @returns {HTMLElement}
  1779. * @memberof CustomContextMenu
  1780. */
  1781. _createBreak() {
  1782. let html = `<div class="context_hr"></div>`;
  1783.  
  1784. let elem = this._createElementsFromHTML(html);
  1785.  
  1786. return elem;
  1787. }
  1788.  
  1789. /**
  1790. *
  1791. *
  1792. * @author Michael Barros <michaelcbarros@gmail.com>
  1793. * @param {string} htmlStr
  1794. * @returns {HTMLElement}
  1795. */
  1796. _createElementsFromHTML(htmlStr) {
  1797. let div = document.createElement('div');
  1798.  
  1799. div.innerHTML = htmlStr.trim();
  1800.  
  1801. return div.firstChild;
  1802. }
  1803.  
  1804. _isHover(elem) {
  1805. return elem.parentElement.querySelector(':hover') === elem;
  1806. }
  1807. }
  1808.  
  1809. /**
  1810. *
  1811. *
  1812. * @author Michael Barros <michaelcbarros@gmail.com>
  1813. * @class LocalStorageEx
  1814. */
  1815. class LocalStorageEx {
  1816. /**
  1817. * Creates an instance of LocalStorageEx.
  1818. *
  1819. * @author Michael Barros <michaelcbarros@gmail.com>
  1820. * @memberof LocalStorageEx
  1821. */
  1822. constructor() {
  1823. this.__storage = localStorage;
  1824. }
  1825.  
  1826. /**
  1827. *
  1828. *
  1829. * @author Michael Barros <michaelcbarros@gmail.com>
  1830. * @readonly
  1831. * @memberof LocalStorageEx
  1832. */
  1833. get UNDEFINED_SAVED_VALUE() {
  1834. return '__** undefined **__';
  1835. }
  1836.  
  1837. /**
  1838. *
  1839. *
  1840. * @author Michael Barros <michaelcbarros@gmail.com>
  1841. * @readonly
  1842. * @memberof LocalStorageEx
  1843. */
  1844. get size() {
  1845. let total = 0;
  1846.  
  1847. for (let x in this.__storage) {
  1848. // Value is multiplied by 2 due to data being stored in `utf-16` format, which requires twice the space.
  1849. let amount = this.__storage[x].length * 2;
  1850.  
  1851. if (!isNaN(amount) && this.__storage.hasOwnProperty(x)) {
  1852. total += amount;
  1853. }
  1854. }
  1855.  
  1856. return total;
  1857. }
  1858.  
  1859. /**
  1860. * Determine if browser supports local storage.
  1861. *
  1862. * @author Michael Barros <michaelcbarros@gmail.com>
  1863. * @returns {boolean}
  1864. * @memberof LocalStorageEx
  1865. */
  1866. isSupported() {
  1867. return typeof Storage !== 'undefined';
  1868. }
  1869.  
  1870. /**
  1871. * Check if key exists in local storage.
  1872. *
  1873. * @author Michael Barros <michaelcbarros@gmail.com>
  1874. * @param {*} key
  1875. * @returns {boolean}
  1876. * @memberof LocalStorageEx
  1877. */
  1878. has(key) {
  1879. if (typeof key === 'object') {
  1880. key = JSON.stringify(key);
  1881. }
  1882.  
  1883. return this.__storage.hasOwnProperty(key);
  1884. }
  1885.  
  1886. /**
  1887. * Retrieve an object from local storage.
  1888. *
  1889. * @author Michael Barros <michaelcbarros@gmail.com>
  1890. * @param {*} key
  1891. * @param {*} [defaultValue=null]
  1892. * @returns {*}
  1893. * @memberof LocalStorageEx
  1894. */
  1895. get(key, defaultValue = null) {
  1896. if (typeof key === 'object') {
  1897. key = JSON.stringify(key);
  1898. }
  1899.  
  1900. if (!this.has(key)) {
  1901. return defaultValue;
  1902. }
  1903.  
  1904. let item = this.__storage.getItem(key);
  1905.  
  1906. try {
  1907. if (item === '__** undefined **__') {
  1908. return undefined;
  1909. } else {
  1910. return JSON.parse(item);
  1911. }
  1912. } catch (error) {
  1913. return item;
  1914. }
  1915. }
  1916.  
  1917. /**
  1918. * Save some value to local storage.
  1919. *
  1920. * @author Michael Barros <michaelcbarros@gmail.com>
  1921. * @param {string} key
  1922. * @param {*} value
  1923. * @returns {void}
  1924. * @memberof LocalStorageEx
  1925. */
  1926. set(key, value) {
  1927. if (typeof key === 'object') {
  1928. key = JSON.stringify(key);
  1929. }
  1930.  
  1931. if (value === undefined) {
  1932. value = this.UNDEFINED_SAVED_VALUE;
  1933. } else if (typeof value === 'object') {
  1934. value = JSON.stringify(value);
  1935. }
  1936.  
  1937. this.__storage.setItem(key, value);
  1938. }
  1939.  
  1940. /**
  1941. * Remove element from local storage.
  1942. *
  1943. * @author Michael Barros <michaelcbarros@gmail.com>
  1944. * @param {*} key
  1945. * @returns {void}
  1946. * @memberof LocalStorageEx
  1947. */
  1948. remove(key) {
  1949. if (typeof key === 'object') {
  1950. key = JSON.stringify(key);
  1951. }
  1952.  
  1953. this.__storage.removeItem(key);
  1954. }
  1955.  
  1956. toString() {
  1957. return JSON.parse(JSON.stringify(this.__storage));
  1958. }
  1959. }
  1960.  
  1961. /**
  1962. *
  1963. *
  1964. * @author Michael Barros <michaelcbarros@gmail.com>
  1965. * @class SessionStorageEx
  1966. * @extends {LocalStorageEx}
  1967. */
  1968. class SessionStorageEx extends LocalStorageEx {
  1969. /**
  1970. * Creates an instance of SessionStorageEx.
  1971. *
  1972. * @author Michael Barros <michaelcbarros@gmail.com>
  1973. * @memberof SessionStorageEx
  1974. */
  1975. constructor() {
  1976. super();
  1977.  
  1978. this.__storage = sessionStorage;
  1979. }
  1980. }
  1981.  
  1982. class IgnoreCaseMap extends Map {
  1983. /**
  1984. *
  1985. *
  1986. * @author Michael Barros <michaelcbarros@gmail.com>
  1987. * @param {string} key
  1988. * @param {*} value
  1989. * @returns {this}
  1990. * @memberof IgnoreCaseMap
  1991. */
  1992. set(key, value) {
  1993. return super.set(key.toLocaleLowerCase(), value);
  1994. }
  1995.  
  1996. /**
  1997. *
  1998. *
  1999. * @author Michael Barros <michaelcbarros@gmail.com>
  2000. * @param {string} key
  2001. * @returns {*}
  2002. * @memberof IgnoreCaseMap
  2003. */
  2004. get(key) {
  2005. return super.get(key.toLocaleLowerCase());
  2006. }
  2007. }
  2008.  
  2009. // #endregion Helper Classes
  2010.  
  2011. // #region Helper Functions
  2012.  
  2013. /**
  2014. *
  2015. *
  2016. * @author Michael Barros <michaelcbarros@gmail.com>
  2017. * @param {string} name
  2018. * @param {{logLevel: log.LogLevelDesc, tag: string}} logLevel
  2019. * @return {log.Logger}
  2020. */
  2021. function getLogger(name, { logLevel, tag }) {
  2022. prefix.reg(log);
  2023.  
  2024. const colors = {
  2025. TRACE: '220;86;220',
  2026. DEBUG: '86;86;220',
  2027. INFO: '134;134;221',
  2028. WARN: '220;220;86',
  2029. ERROR: '220;86;86',
  2030. };
  2031.  
  2032. /** @type {prefix.LoglevelPluginPrefixOptions} */
  2033. let options = {
  2034. // template: tag ? `[%t] %l [${tag}] %n:` : '[%t] %l %n:',
  2035. levelFormatter: function (level) {
  2036. return level.toUpperCase();
  2037. },
  2038. nameFormatter: function (name) {
  2039. return name || 'root';
  2040. },
  2041. timestampFormatter: function (date) {
  2042. return date.toTimeString().replace(/.*(\d{2}:\d{2}:\d{2}).*/, '$1');
  2043. },
  2044. format: function (level, name, timestamp) {
  2045. let _timestamp = `\x1B[90m[${timestamp}]\x1B[m`;
  2046. let _level = `\x1B[38;2;${colors[level.toUpperCase()]}m${level.toUpperCase()}\x1B[m`;
  2047. let _name = `\x1B[38;2;38;177;38m${tag ? `[${tag}-` : '['}${name}]\x1B[m`;
  2048.  
  2049. let _format = `${_timestamp} ${_level} ${_name}:`;
  2050.  
  2051. return _format;
  2052. },
  2053. };
  2054.  
  2055. const logger = log.getLogger(name);
  2056.  
  2057. prefix.apply(logger, options);
  2058.  
  2059. logger.setLevel(logLevel || 'WARN');
  2060.  
  2061. return logger;
  2062. }
  2063.  
  2064. function pp(obj, fn) {
  2065. fn = fn || console.log;
  2066.  
  2067. fn(pformat(obj));
  2068. }
  2069.  
  2070. function pformat(obj, space = 4) {
  2071. return JSON.stringify(obj, null, space);
  2072. }
  2073.  
  2074. function removeAllButLastStrPattern(string, token) {
  2075. let parts = string.split(token);
  2076.  
  2077. if (parts[1] === undefined) return string;
  2078. else return parts.slice(0, -1).join('') + token + parts.slice(-1);
  2079. }
  2080.  
  2081. /**
  2082. *
  2083. *
  2084. * @author Michael Barros <michaelcbarros@gmail.com>
  2085. * @param {Array.<T> | Array} arr
  2086. * @param {?function(T, T): boolean} callbackObjs
  2087. * @return {T[]}
  2088. * @template T
  2089. */
  2090. function dedupeArr(arr, callbackObjs) {
  2091. if (callbackObjs) {
  2092. let tempArr = /** @type {[]} */ (arr).filter((value, index) => {
  2093. return (
  2094. index ===
  2095. arr.findIndex((other) => {
  2096. return callbackObjs(value, other);
  2097. })
  2098. );
  2099. });
  2100.  
  2101. return tempArr;
  2102. } else {
  2103. return [...new Set(arr)];
  2104. }
  2105. }
  2106.  
  2107. /**
  2108. *
  2109. *
  2110. * @author Michael Barros <michaelcbarros@gmail.com>
  2111. * @param {any} obj
  2112. * @returns {boolean}
  2113. */
  2114. function isClass(obj) {
  2115. return typeof obj === 'function' && /^\s*class\s+/.test(obj.toString());
  2116. }
  2117.  
  2118. /**
  2119. * Checks whether a variable is a class or an instance created with `new`.
  2120. *
  2121. * @author Michael Barros <michaelcbarros@gmail.com>
  2122. * @param {*} value The variable to check.
  2123. * @returns {boolean} `true` if the variable is a class or an instance created with `new`, `false` otherwise.
  2124. */
  2125. function isClassOrInstance(value) {
  2126. // prettier-ignore
  2127. if (typeof value === 'function' &&
  2128. value.prototype &&
  2129. typeof value.prototype.constructor === 'function' &&
  2130. value.prototype.constructor !== Array &&
  2131. value.prototype.constructor !== Object) {
  2132. return true; // It's a class
  2133. } else if (typeof value === 'object' &&
  2134. value.constructor &&
  2135. typeof value.constructor === 'function' &&
  2136. value.constructor.prototype &&
  2137. typeof value.constructor.prototype.constructor === 'function' &&
  2138. value.constructor !== Array &&
  2139. value.constructor !== Object) {
  2140. return true; // It's an instance created with new
  2141. }
  2142.  
  2143. return false;
  2144. }
  2145.  
  2146. /**
  2147. *
  2148. *
  2149. * @author Michael Barros <michaelcbarros@gmail.com>
  2150. * @param {*} value The variable to check.
  2151. * @returns {boolean}
  2152. */
  2153. function isFunction(value) {
  2154. try {
  2155. return typeof value == 'function';
  2156. } catch (error) {
  2157. return false;
  2158. }
  2159. }
  2160.  
  2161. /**
  2162. *
  2163. *
  2164. * @author Michael Barros <michaelcbarros@gmail.com>
  2165. * @param {object} obj
  2166. * @param {{ propsToExclude?: string[]; namesOnly: boolean; removeDuplicates: boolean; asObject: boolean }} [{ propsToExclude = [], namesOnly = false, removeDuplicates = true, asObject = false } = {}]
  2167. * @returns
  2168. */
  2169. function getObjProps(obj, { propsToExclude = [], namesOnly = false, removeDuplicates = true, asObject = false } = {}) {
  2170. // Default
  2171. let _propsToExclude = [
  2172. //
  2173. '__defineGetter__',
  2174. '__defineSetter__',
  2175. '__lookupSetter__',
  2176. '__lookupGetter__',
  2177. '__proto__',
  2178. '__original__',
  2179.  
  2180. 'caller',
  2181. 'callee',
  2182. 'arguments',
  2183.  
  2184. 'toString',
  2185. 'valueOf',
  2186. 'constructor',
  2187. 'hasOwnProperty',
  2188. 'isPrototypeOf',
  2189. 'propertyIsEnumerable',
  2190. 'toLocaleString',
  2191. ];
  2192.  
  2193. _propsToExclude = propsToExclude && Array.isArray(propsToExclude) ? _propsToExclude.concat(propsToExclude) : _propsToExclude;
  2194.  
  2195. let objHierarchy = getObjHierarchy(obj);
  2196. let propNames = getPropNames(objHierarchy);
  2197. let plainObj = {};
  2198. let objKeys = [];
  2199.  
  2200. /**
  2201. *
  2202. *
  2203. * @author Michael Barros <michaelcbarros@gmail.com>
  2204. * @param {any} obj
  2205. * @returns {Array<any>}
  2206. */
  2207. function getObjHierarchy(obj) {
  2208. let objs = [obj];
  2209.  
  2210. obj = isClassOrInstance(obj) ? obj.prototype || obj.__proto__ : obj;
  2211.  
  2212. do {
  2213. objs.push(obj);
  2214. } while ((obj = Object.getPrototypeOf(obj)));
  2215.  
  2216. return objs;
  2217. }
  2218.  
  2219. /**
  2220. *
  2221. *
  2222. * @author Michael Barros <michaelcbarros@gmail.com>
  2223. * @param {Array<any>} objHierarchy
  2224. * @returns {string[]}
  2225. */
  2226. function getPropNames(objHierarchy) {
  2227. /** @type {string[]} */
  2228. let propNames = [];
  2229.  
  2230. for (let i = 0; i < objHierarchy.length; i++) {
  2231. const _obj = objHierarchy[i];
  2232.  
  2233. let getPropFuncs = [Object.getOwnPropertyNames, Object.getOwnPropertySymbols];
  2234.  
  2235. getPropFuncs.forEach((func) => {
  2236. let _propNames = func(_obj);
  2237.  
  2238. _propNames.forEach((propName) => {
  2239. if (!_propsToExclude.includes(propName) && !propNames.includes(propName)) {
  2240. propNames.push(propName);
  2241. }
  2242. });
  2243. });
  2244. }
  2245.  
  2246. return propNames;
  2247. }
  2248.  
  2249. /**
  2250. *
  2251. *
  2252. * @author Michael Barros <michaelcbarros@gmail.com>
  2253. * @param {{ name: string, value: any }[]} props
  2254. * @return {{ name: string, value: any }[]}
  2255. */
  2256. function dedupeProps(props) {
  2257. function findNonNullProp(props, name) {
  2258. let res = props.find((prop) => prop.name == name && prop.value != null);
  2259.  
  2260. if (!res) {
  2261. res = props.find((prop) => prop.name == name);
  2262. }
  2263.  
  2264. return res;
  2265. }
  2266.  
  2267. function propsContains(props, name) {
  2268. return props.some((prop) => prop.name == name);
  2269. }
  2270.  
  2271. let newProps = [];
  2272.  
  2273. for (let i = 0; i < props.length; i++) {
  2274. const prop = props[i];
  2275.  
  2276. let tempProp = findNonNullProp(props, prop.name);
  2277.  
  2278. if (!propsContains(newProps, tempProp.name)) {
  2279. newProps.push(tempProp);
  2280. }
  2281. }
  2282.  
  2283. return newProps;
  2284. }
  2285.  
  2286. function getProps(objHierarchy, doFuncs = false) {
  2287. /** @type {{ name: string, value: any }} */
  2288. let props = [];
  2289.  
  2290. for (let o = 0; o < objHierarchy.length; o++) {
  2291. const _obj = objHierarchy[o];
  2292.  
  2293. for (let p = 0; p < propNames.length; p++) {
  2294. const propName = propNames[p];
  2295. let value;
  2296.  
  2297. try {
  2298. value = _obj[propName];
  2299. } catch (error) {}
  2300.  
  2301. if (!_propsToExclude.includes(propName)) {
  2302. if (asObject) {
  2303. if (!objKeys.includes(propName)) {
  2304. objKeys.push(propName);
  2305.  
  2306. plainObj[propName] = value;
  2307. }
  2308. } else {
  2309. props.push({
  2310. name: propName,
  2311. value: value,
  2312. });
  2313. }
  2314. }
  2315. }
  2316. }
  2317.  
  2318. if (!asObject) {
  2319. if (removeDuplicates) {
  2320. props = dedupeProps(props);
  2321. }
  2322.  
  2323. props = props.filter(function (prop, i, props) {
  2324. let exprs = [
  2325. //
  2326. !_propsToExclude.includes(prop.name),
  2327. // props[i + 1] && prop.name != props[i + 1].name,
  2328. ...(doFuncs ? [isFunction(prop.value)] : [!isFunction(prop.value)]),
  2329. ];
  2330.  
  2331. return exprs.every(Boolean);
  2332. });
  2333. }
  2334.  
  2335. if (asObject) {
  2336. return plainObj;
  2337. } else {
  2338. return props.sort(function (a, b) {
  2339. let aName = typeof a.name == 'symbol' ? a.name.toString() : a.name;
  2340. let bName = typeof b.name == 'symbol' ? b.name.toString() : b.name;
  2341.  
  2342. if (aName < bName) return -1;
  2343. if (aName > bName) return 1;
  2344.  
  2345. return 0;
  2346. });
  2347. }
  2348. }
  2349.  
  2350. let res;
  2351.  
  2352. if (asObject) {
  2353. getProps(objHierarchy, true);
  2354. getProps(objHierarchy);
  2355.  
  2356. res = plainObj;
  2357. } else {
  2358. res = {
  2359. funcs: getProps(objHierarchy, true),
  2360. props: getProps(objHierarchy),
  2361. };
  2362.  
  2363. if (namesOnly) {
  2364. res.funcs = res.funcs.filter((func) => func.name.toString() != 'Symbol(Symbol.hasInstance)').map((func) => func.name);
  2365. res.props = res.props.filter((prop) => prop.name.toString() != 'Symbol(Symbol.hasInstance)').map((prop) => prop.name);
  2366. }
  2367. }
  2368.  
  2369. objHierarchy = null;
  2370.  
  2371. return res;
  2372. }
  2373.  
  2374. /**
  2375. *
  2376. *
  2377. * @author Michael Barros <michaelcbarros@gmail.com>
  2378. * @param {Window} [_window=window]
  2379. * @param {{ namesOnly: boolean; asObject: boolean }} [{ namesOnly = false, asObject = false } = {}]
  2380. * @returns
  2381. */
  2382. function getUserDefinedGlobalProps(_window = null, { namesOnly = false, asObject = false } = {}) {
  2383. _window = _window || getWindow();
  2384.  
  2385. let iframe = document.createElement('iframe');
  2386.  
  2387. iframe.style.display = 'none';
  2388.  
  2389. document.body.appendChild(iframe);
  2390.  
  2391. let plainObj = {};
  2392. let objKeys = [];
  2393.  
  2394. function getProps(obj, doFuncs = false) {
  2395. let props = [];
  2396. let _obj = obj;
  2397.  
  2398. let getPropFuncs = [Object.getOwnPropertyNames, Object.getOwnPropertySymbols];
  2399.  
  2400. getPropFuncs.forEach((func) => {
  2401. let propNames = func(_obj);
  2402.  
  2403. for (let i = 0; i < propNames.length; i++) {
  2404. const propName = propNames[i];
  2405. let value;
  2406.  
  2407. try {
  2408. value = _obj[propName];
  2409. } catch (error) {}
  2410.  
  2411. if (isNumber(propName) && value?.constructor?.name == 'Window') continue;
  2412.  
  2413. if (!iframe.contentWindow.hasOwnProperty(propName)) {
  2414. if (asObject) {
  2415. if (!objKeys.includes(propName)) {
  2416. objKeys.push(propName);
  2417.  
  2418. plainObj[propName] = value;
  2419. }
  2420. } else {
  2421. props.push({
  2422. name: propName,
  2423. value: value,
  2424. });
  2425. }
  2426. }
  2427. }
  2428. });
  2429.  
  2430. if (!asObject) {
  2431. props = props.filter(function (prop, i, props) {
  2432. let propName1 = prop.name;
  2433. let propName2 = props[i + 1] ? props[i + 1].name : undefined;
  2434. let propValue1 = prop.value;
  2435. let propValue2 = props[i + 1] ? props[i + 1].value : undefined;
  2436.  
  2437. let exprs = [
  2438. //
  2439. // props[i + 1] && propName1 != propName2,
  2440. (props[i + 1] && propName1.constructor.name == 'Symbol' && propName2.constructor.name == 'Symbol' && propValue1 != propValue2) || propName1 != propName2,
  2441. ...(doFuncs ? [isFunction(obj[propName1])] : [!isFunction(obj[propName1])]),
  2442. ];
  2443.  
  2444. return exprs.every(Boolean);
  2445. });
  2446. }
  2447.  
  2448. if (asObject) {
  2449. return plainObj;
  2450. } else {
  2451. return props.sort(function (a, b) {
  2452. let aName = typeof a.name == 'symbol' ? a.name.toString() : a.name;
  2453. let bName = typeof b.name == 'symbol' ? b.name.toString() : b.name;
  2454.  
  2455. if (aName < bName) return -1;
  2456. if (aName > bName) return 1;
  2457.  
  2458. return 0;
  2459. });
  2460. }
  2461. }
  2462.  
  2463. let res;
  2464.  
  2465. if (asObject) {
  2466. getProps(_window, true);
  2467. getProps(_window);
  2468.  
  2469. res = plainObj;
  2470. } else {
  2471. res = {
  2472. funcs: getProps(_window, true),
  2473. props: getProps(_window),
  2474. };
  2475.  
  2476. if (namesOnly) {
  2477. res.funcs = res.funcs.filter((func) => func.name.toString() != 'Symbol(Symbol.hasInstance)').map((func) => func.name);
  2478. res.props = res.props.filter((prop) => prop.name.toString() != 'Symbol(Symbol.hasInstance)').map((prop) => prop.name);
  2479. }
  2480. }
  2481.  
  2482. document.body.removeChild(iframe);
  2483.  
  2484. return res;
  2485. }
  2486.  
  2487. /**
  2488. *
  2489. *
  2490. * @author Michael Barros <michaelcbarros@gmail.com>
  2491. * @param {T} obj
  2492. * @param {T | boolean} thisArg
  2493. * @returns {T}
  2494. * @template T
  2495. */
  2496. function storeObjOriginalFuncs(obj, thisArg = true) {
  2497. let props = getObjProps(obj);
  2498.  
  2499. obj.__original__ = {};
  2500.  
  2501. for (let i = 0; i < props.funcs.length; i++) {
  2502. const func = props.funcs[i];
  2503.  
  2504. if (thisArg == true) {
  2505. obj.__original__[func.name] = func.value.bind(obj);
  2506. } else {
  2507. obj.__original__[func.name] = thisArg != false && thisArg != null && thisArg != undefined ? func.value.bind(thisArg) : func.value;
  2508. }
  2509. }
  2510.  
  2511. return obj;
  2512. }
  2513.  
  2514. function printProps(obj, title) {
  2515. let headerFooterBanner = '*********************************************************';
  2516.  
  2517. console.log(headerFooterBanner);
  2518. console.log(`* ${title || ''}`);
  2519. console.log(headerFooterBanner);
  2520.  
  2521. for (let key in obj) console.log(key + ': ', [obj[key]]);
  2522.  
  2523. console.log(headerFooterBanner);
  2524. }
  2525.  
  2526. function sortObject(o, desc) {
  2527. let sorted = {};
  2528. let key;
  2529. let a = [];
  2530.  
  2531. for (key in o) {
  2532. if (o.hasOwnProperty(key)) a.push(key);
  2533. }
  2534.  
  2535. if (desc) a.sort(sortDescending);
  2536. else a.sort(sortAscending);
  2537.  
  2538. for (key = 0; key < a.length; key++) sorted[a[key]] = o[a[key]];
  2539.  
  2540. return sorted;
  2541. }
  2542.  
  2543. function sortAscending(a, b) {
  2544. if (typeof a == 'string') {
  2545. a = a.toLowerCase();
  2546. b = b.toLowerCase();
  2547. }
  2548.  
  2549. if (a < b) return -1;
  2550. else if (a > b) return 1;
  2551. else return 0;
  2552. }
  2553.  
  2554. function sortDescending(a, b) {
  2555. if (typeof a == 'string') {
  2556. a = a.toLowerCase();
  2557. b = b.toLowerCase();
  2558. }
  2559.  
  2560. if (a > b) return -1;
  2561. else if (a < b) return 1;
  2562. else return 0;
  2563. }
  2564.  
  2565. function getFileExtension(sFile) {
  2566. return sFile.replace(/^(.*)(\.[^/.]+)$/, '$2');
  2567. }
  2568.  
  2569. /**
  2570. * Async wait function.
  2571. * Example:
  2572. * (async () => {
  2573. * await wait(4000).then(() => {
  2574. * console.log(new Date().toLocaleTimeString());
  2575. * }).then(() => {
  2576. * console.log('here');
  2577. * });
  2578. * })();
  2579. *
  2580. * @param {number} ms - Milliseconds to wait.
  2581. * @param {boolean} [synchronous=false] - Wait synchronously.
  2582. */
  2583. async function wait(ms, synchronous = false) {
  2584. let _wait = (ms, synchronous) => {
  2585. if (synchronous) {
  2586. let start = Date.now();
  2587. let now = start;
  2588.  
  2589. while (now - start < ms) now = Date.now();
  2590. } else {
  2591. return new Promise((resolve) => setTimeout(resolve, ms));
  2592. }
  2593. };
  2594.  
  2595. await _wait(ms, synchronous);
  2596. }
  2597.  
  2598. /**
  2599. *
  2600. *
  2601. * @author Michael Barros <michaelcbarros@gmail.com>
  2602. * @param {() => bool} condition
  2603. * @param {{ timeout?: number; callback: () => T; conditionIsAsync: boolean; }} [{ timeout, callback, conditionIsAsync = false } = {}]
  2604. * @returns {T}
  2605. * @template T
  2606. */
  2607. async function waitUntil(condition, { timeout, callback, conditionIsAsync = false } = {}) {
  2608. timeout = timeout || -1;
  2609. let maxTime = timeout == -1 ? 20000 : -1;
  2610. let startTime = new Date();
  2611.  
  2612. let timeRanOut = false;
  2613.  
  2614. let done = (() => {
  2615. let deferred = {};
  2616.  
  2617. deferred.promise = new Promise((resolve, reject) => {
  2618. deferred.resolve = resolve;
  2619. deferred.reject = reject;
  2620. });
  2621.  
  2622. return deferred;
  2623. })();
  2624.  
  2625. /** @type {number} */
  2626. let timeoutId;
  2627.  
  2628. if (timeout && timeout > 0) {
  2629. timeoutId = setTimeout(() => {
  2630. timeRanOut = true;
  2631.  
  2632. return done.reject();
  2633. }, timeout);
  2634. }
  2635.  
  2636. let loop = async () => {
  2637. let endTime = new Date();
  2638. let elapsed = endTime - startTime;
  2639.  
  2640. let conditionResult = conditionIsAsync ? await condition() : condition();
  2641.  
  2642. if (conditionResult || timeRanOut || (maxTime != -1 && elapsed > maxTime)) {
  2643. clearTimeout(timeoutId);
  2644.  
  2645. return done.resolve(callback ? await callback() : undefined);
  2646. }
  2647.  
  2648. setTimeout(loop, 0);
  2649. };
  2650.  
  2651. setTimeout(loop, 0);
  2652.  
  2653. return done.promise;
  2654. }
  2655.  
  2656. /**
  2657. *
  2658. *
  2659. * @author Michael Barros <michaelcbarros@gmail.com>
  2660. * @param {any} obj
  2661. * @param {boolean} [getInherited=false]
  2662. * @returns {string}
  2663. */
  2664. function getType(obj, getInherited = false) {
  2665. let _typeVar = (function (global) {
  2666. let cache = {};
  2667.  
  2668. return function (obj) {
  2669. let key;
  2670.  
  2671. // null
  2672. if (obj == null) return 'null';
  2673.  
  2674. // window/global
  2675. if (obj == global) return 'global';
  2676.  
  2677. // basic: string, boolean, number, undefined
  2678. if (!['object', 'function'].includes((key = typeof obj))) return key;
  2679.  
  2680. if (obj.constructor != undefined && obj.constructor.name != 'Object' && !getInherited) return obj.constructor.name;
  2681.  
  2682. // cached. date, regexp, error, object, array, math
  2683. // and get XXXX from [object XXXX], and cache it
  2684. return cache[(key = {}.toString.call(obj))] || (cache[key] = key.slice(8, -1));
  2685. };
  2686. })(globalThis);
  2687.  
  2688. return _typeVar(obj);
  2689. }
  2690.  
  2691. /**
  2692. * Returns a function, that, as long as it continues to be invoked, will not
  2693. * be triggered. The function will be called after it stops being called for
  2694. * N milliseconds. If `immediate` is passed, trigger the function on the
  2695. * leading edge, instead of the trailing.
  2696. *
  2697. * @param {function} func
  2698. * @param {Number} wait
  2699. * @param {Boolean} immediate
  2700. * @returns
  2701. */
  2702. function debounce(func, wait, immediate) {
  2703. let timeout;
  2704.  
  2705. return function () {
  2706. let context = this,
  2707. args = arguments;
  2708.  
  2709. let later = function () {
  2710. timeout = null;
  2711.  
  2712. if (!immediate) func.apply(context, args);
  2713. };
  2714.  
  2715. let callNow = immediate && !timeout;
  2716.  
  2717. clearTimeout(timeout);
  2718. timeout = setTimeout(later, wait);
  2719.  
  2720. if (callNow) func.apply(context, args);
  2721. };
  2722. }
  2723.  
  2724. function equals(x, y) {
  2725. if (x === y) return true;
  2726. // if both x and y are null or undefined and exactly the same
  2727.  
  2728. if (!(x instanceof Object) || !(y instanceof Object)) return false;
  2729. // if they are not strictly equal, they both need to be Objects
  2730.  
  2731. if (x.constructor !== y.constructor) return false;
  2732. // they must have the exact same prototype chain, the closest we can do is
  2733. // test there constructor.
  2734.  
  2735. for (let p in x) {
  2736. if (!x.hasOwnProperty(p)) continue;
  2737. // other properties were tested using x.constructor === y.constructor
  2738.  
  2739. if (!y.hasOwnProperty(p)) return false;
  2740. // allows to compare x[ p ] and y[ p ] when set to undefined
  2741.  
  2742. if (x[p] === y[p]) continue;
  2743. // if they have the same strict value or identity then they are equal
  2744.  
  2745. if (typeof x[p] !== 'object') return false;
  2746. // Numbers, Strings, Functions, Booleans must be strictly equal
  2747.  
  2748. if (!equals(x[p], y[p])) return false;
  2749. // Objects and Arrays must be tested recursively
  2750. }
  2751.  
  2752. for (p in y) {
  2753. if (y.hasOwnProperty(p) && !x.hasOwnProperty(p)) return false;
  2754. // allows x[ p ] to be set to undefined
  2755. }
  2756. return true;
  2757. }
  2758.  
  2759. function isEncoded(uri) {
  2760. uri = uri || '';
  2761.  
  2762. return uri !== decodeURIComponent(uri);
  2763. }
  2764.  
  2765. function fullyDecodeURI(uri) {
  2766. while (isEncoded(uri)) uri = decodeURIComponent(uri);
  2767.  
  2768. return uri;
  2769. }
  2770.  
  2771. /**
  2772. * Get difference in days between two dates.
  2773. *
  2774. * @param {Date} a
  2775. * @param {Date} b
  2776. * @returns
  2777. */
  2778. function dateDiffInDays(a, b) {
  2779. let _MS_PER_DAY = 1000 * 60 * 60 * 24;
  2780.  
  2781. // Discard the time and time-zone information.
  2782. let utc1 = Date.UTC(a.getFullYear(), a.getMonth(), a.getDate());
  2783. let utc2 = Date.UTC(b.getFullYear(), b.getMonth(), b.getDate());
  2784.  
  2785. return Math.floor((utc1 - utc2) / _MS_PER_DAY);
  2786. }
  2787.  
  2788. function randomInt(min, max) {
  2789. return Math.floor(Math.random() * (max - min + 1) + min);
  2790. }
  2791.  
  2792. function randomFloat(min, max) {
  2793. return Math.random() * (max - min + 1) + min;
  2794. }
  2795.  
  2796. function keySort(keys, desc) {
  2797. return function (a, b) {
  2798. let aVal = null;
  2799. let bVal = null;
  2800.  
  2801. for (let i = 0; i < keys.length; i++) {
  2802. const key = keys[i];
  2803.  
  2804. if (i == 0) {
  2805. aVal = a[key];
  2806. bVal = b[key];
  2807. } else {
  2808. aVal = aVal[key];
  2809. bVal = bVal[key];
  2810. }
  2811. }
  2812. return desc ? ~~(aVal < bVal) : ~~(aVal > bVal);
  2813. };
  2814. }
  2815.  
  2816. function observe(obj, handler) {
  2817. return new Proxy(obj, {
  2818. get(target, key) {
  2819. return target[key];
  2820. },
  2821. set(target, key, value) {
  2822. target[key] = value;
  2823.  
  2824. if (handler) handler();
  2825. },
  2826. });
  2827. }
  2828.  
  2829. /**
  2830. *
  2831. *
  2832. * @author Michael Barros <michaelcbarros@gmail.com>
  2833. * @param {Console} console
  2834. */
  2835. function addSaveToConsole(console) {
  2836. console.save = function (data, filename) {
  2837. if (!data) {
  2838. console.error('Console.save: No data');
  2839.  
  2840. return;
  2841. }
  2842.  
  2843. if (!filename) filename = 'console.json';
  2844.  
  2845. if (typeof data === 'object') data = JSON.stringify(data, undefined, 4);
  2846.  
  2847. let blob = new Blob([data], {
  2848. type: 'text/json',
  2849. });
  2850. let event = document.createEvent('MouseEvents');
  2851. let tempElem = document.createElement('a');
  2852.  
  2853. tempElem.download = filename;
  2854. tempElem.href = window.URL.createObjectURL(blob);
  2855. tempElem.dataset.downloadurl = ['text/json', tempElem.download, tempElem.href].join(':');
  2856.  
  2857. event.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
  2858. tempElem.dispatchEvent(event);
  2859. };
  2860. }
  2861.  
  2862. /**
  2863. *
  2864. *
  2865. * @author Michael Barros <michaelcbarros@gmail.com>
  2866. * @param {Window} _window
  2867. * @param {string} propName
  2868. * @param {{} | [] | any} value
  2869. */
  2870. function setupWindowProps(_window, propName, value) {
  2871. if (getType(value) == 'object') {
  2872. if (typeof _window[propName] === 'undefined' || _window[propName] == null) {
  2873. _window[propName] = {};
  2874. }
  2875.  
  2876. let keys = Object.keys(value);
  2877.  
  2878. for (let i = 0; i < keys.length; i++) {
  2879. const key = keys[i];
  2880.  
  2881. if (!(/** @type {{}} */ (_window[propName].hasOwnProperty(key)))) {
  2882. _window[propName][key] = null;
  2883. }
  2884.  
  2885. if (_window[propName][key] == null) {
  2886. _window[propName][key] = value[key];
  2887. }
  2888. }
  2889. } else {
  2890. if (typeof _window[propName] === 'undefined' || _window[propName] == null) {
  2891. _window[propName] = value;
  2892. }
  2893. }
  2894. }
  2895.  
  2896. /**
  2897. *
  2898. *
  2899. * @author Michael Barros <michaelcbarros@gmail.com>
  2900. * @param {{ name: string; value: any; }[]} variables
  2901. */
  2902. function exposeGlobalVariables(variables) {
  2903. variables.forEach((variable, index, variables) => {
  2904. try {
  2905. setupWindowProps(getWindow(), variable.name, variable.value);
  2906. } catch (error) {
  2907. logger.error(`Unable to expose variable ${variable.name} into the global scope.`);
  2908. }
  2909. });
  2910. }
  2911.  
  2912. /**
  2913. *
  2914. *
  2915. * @author Michael Barros <michaelcbarros@gmail.com>
  2916. * @param {string} str
  2917. * @returns {string}
  2918. */
  2919. function htmlEntitiesDecode(str) {
  2920. return str
  2921. .replace(/&amp;/g, '&')
  2922. .replace(/&lt;/g, '<')
  2923. .replace(/&gt;/g, '>')
  2924. .replace(/&quot;/g, '"');
  2925. }
  2926.  
  2927. /**
  2928. *
  2929. *
  2930. * @author Michael Barros <michaelcbarros@gmail.com>
  2931. * @param {string} metaName
  2932. * @returns {string}
  2933. */
  2934. function getMeta(metaName) {
  2935. const metas = document.getElementsByTagName('meta');
  2936.  
  2937. for (let i = 0; i < metas.length; i++) {
  2938. if (metas[i].getAttribute('name') === metaName) {
  2939. return metas[i].getAttribute('content');
  2940. }
  2941. }
  2942.  
  2943. return '';
  2944. }
  2945.  
  2946. /**
  2947. *
  2948. *
  2949. * @returns {Window & typeof globalThis}
  2950. */
  2951. function getWindow() {
  2952. return globalThis.GM_info && GM_info.script.grant.includes('unsafeWindow') ? unsafeWindow : globalThis;
  2953. }
  2954.  
  2955. /**
  2956. *
  2957. *
  2958. * @param {Window} _window
  2959. */
  2960. function getTopWindow(_window = null) {
  2961. _window = _window || getWindow();
  2962.  
  2963. try {
  2964. if (_window.self !== _window.top) {
  2965. _window = getTopWindow(_window.parent);
  2966. }
  2967. } catch (e) {}
  2968.  
  2969. return _window;
  2970. }
  2971.  
  2972. /**
  2973. * Setup global error handler
  2974. *
  2975. * **Example:**
  2976. * ```javascript
  2977. * setupGlobalErrorHandler({
  2978. * callback: (error) => console.error('Error:', error),
  2979. * continuous: true,
  2980. * prevent_default: true,
  2981. * tag: '[test-global-error-handler]',
  2982. * });
  2983. * ```
  2984. *
  2985. * @author Michael Barros <michaelcbarros@gmail.com>
  2986. * @param {{ callback: (error: ErrorEx) => void; continuous?: boolean; prevent_default?: boolean; tag?: string; logFunc?: (...data: any[]) => void; _window: Window; }} [{ callback, continuous = true, prevent_default = false, tag = '[akkd]', logFunc = console.error, _window = window } = {}]
  2987. */
  2988. function setupGlobalErrorHandler({ callback, continuous = true, prevent_default = false, tag = null, logFunc = console.error, _window = window } = {}) {
  2989. // respect existing onerror handlers
  2990. let _onerror_original = _window.onerror;
  2991.  
  2992. // install our new error handler
  2993. _window.onerror = function (event, source, lineno, colno, error) {
  2994. if (_onerror_original) {
  2995. _onerror_original(event, source, lineno, colno, error);
  2996. }
  2997.  
  2998. // unset onerror to prevent loops and spamming
  2999. let _onerror = _window.onerror;
  3000.  
  3001. _window.onerror = null;
  3002.  
  3003. // now deal with the error
  3004. let errorObject = new ErrorEx(event, source, lineno, colno, error);
  3005. let errorMessage = createErrorMessage(errorObject);
  3006.  
  3007. if (tag) {
  3008. let rgb = '38;177;38';
  3009.  
  3010. tag = `\x1B[38;2;${rgb}m${tag}\x1B[m`;
  3011.  
  3012. logFunc(tag, errorMessage);
  3013. } else {
  3014. logFunc(errorMessage);
  3015. }
  3016.  
  3017. // run callback if provided
  3018. if (callback) {
  3019. callback(errorObject);
  3020. }
  3021.  
  3022. // re-install this error handler again if continuous mode
  3023. if (continuous) {
  3024. _window.onerror = _onerror;
  3025. }
  3026.  
  3027. // true if normal error propagation should be suppressed
  3028. // (i.e. normally console.error is logged by the browser)
  3029. return prevent_default;
  3030. };
  3031.  
  3032. class ErrorEx {
  3033. /**
  3034. * Creates an instance of ErrorEx.
  3035. * @author Michael Barros <michaelcbarros@gmail.com>
  3036. * @param {string | Event} event
  3037. * @param {string} source
  3038. * @param {number} lineno
  3039. * @param {number} colno
  3040. * @param {Error} error
  3041. * @memberof ErrorEx
  3042. */
  3043. constructor(event, source, lineno, colno, error) {
  3044. this.name = error.name;
  3045. this.message = error && error.message ? error.message : null;
  3046. this.stack = error && error.stack ? error.stack : null;
  3047. this.event = event;
  3048. this.location = document.location.href;
  3049. this.url = source;
  3050. this.lineno = lineno;
  3051. this.colno = colno;
  3052. this.useragent = navigator.userAgent;
  3053. this.fileName = error && error.fileName ? error.fileName : null;
  3054. this.description = error && error.description ? error.description : null;
  3055. this.name = error && error.name ? error.name : null;
  3056. this.error = error;
  3057. }
  3058. }
  3059.  
  3060. /**
  3061. *
  3062. *
  3063. * @author Michael Barros <michaelcbarros@gmail.com>
  3064. * @param {ErrorEx} error
  3065. * @returns {string}
  3066. */
  3067. function createErrorMessage(error) {
  3068. let name = error && error.name ? error.name : 'Error';
  3069. let message = error && error.message ? error.message : 'Unknown error occured';
  3070. let stack = error && error.stack ? error.stack.split('\n').splice(1).join('\n') : 'Error';
  3071.  
  3072. let errorMessage = `Uncaught Global ${name}: ${message}\n${stack}`;
  3073.  
  3074. return errorMessage;
  3075. }
  3076. }
  3077.  
  3078. function applyCss(cssFiles) {
  3079. /** @type {{ css: string, node?: HTMLElement }[]} */
  3080. let cssArr = [];
  3081.  
  3082. for (let i = 0; i < cssFiles.length; i++) {
  3083. let cssStr = GM_getResourceText(cssFiles[i]);
  3084.  
  3085. cssArr.push({
  3086. css: cssStr,
  3087. });
  3088. }
  3089.  
  3090. addStyles(cssArr);
  3091. }
  3092.  
  3093. function applyCss2(cssFiles) {
  3094. /**
  3095. *
  3096. *
  3097. * @author Michael Barros <michaelcbarros@gmail.com>
  3098. * @param {string} cssStyleStr
  3099. * @returns {HTMLStyleElement}
  3100. */
  3101. function createStyleElementFromCss(cssStyleStr) {
  3102. let style = document.createElement('style');
  3103.  
  3104. style.innerHTML = cssStyleStr.trim();
  3105.  
  3106. return style;
  3107. }
  3108.  
  3109. let ranOnce = false;
  3110.  
  3111. /** @type {HTMLStyleElement[]} */
  3112. getWindow().akkd.styleElements = [];
  3113.  
  3114. function removeStyleElements() {
  3115. for (let i = 0; i < getWindow().akkd.styleElements.length; i++) {
  3116. let styleElement = getWindow().akkd.styleElements[i];
  3117.  
  3118. styleElement.remove();
  3119. }
  3120.  
  3121. getWindow().akkd.styleElements = [];
  3122. }
  3123.  
  3124. function _editStyleSheets() {
  3125. $(document).arrive('style, link', async function () {
  3126. if (this.tagName == 'LINK' && this.href.includes('.css')) {
  3127. removeStyleElements();
  3128.  
  3129. for (let i = 0; i < cssFiles.length; i++) {
  3130. let cssFile = cssFiles[i];
  3131. let css = GM_getResourceText(cssFile);
  3132.  
  3133. let styleElem = createStyleElementFromCss(css);
  3134.  
  3135. styleElem.id = `akkd-transform-style-${(i + 1).toString().padStart(2, '0')}`;
  3136.  
  3137. getWindow().akkd.styleElements.push(styleElem);
  3138.  
  3139. document.body.appendChild(styleElem);
  3140. }
  3141. }
  3142. });
  3143.  
  3144. if (!ranOnce) {
  3145. for (let i = 0; i < cssFiles.length; i++) {
  3146. let cssFile = cssFiles[i];
  3147. let css = GM_getResourceText(cssFile);
  3148.  
  3149. let styleElem = createStyleElementFromCss(css);
  3150.  
  3151. styleElem.id = `akkd-transform-style-${(i + 1).toString().padStart(2, '0')}`;
  3152.  
  3153. getWindow().akkd.styleElements.push(styleElem);
  3154.  
  3155. document.body.appendChild(styleElem);
  3156. }
  3157.  
  3158. ranOnce = true;
  3159. }
  3160. }
  3161.  
  3162. _editStyleSheets();
  3163. }
  3164.  
  3165. /**
  3166. *
  3167. *
  3168. * @author Michael Barros <michaelcbarros@gmail.com>
  3169. * @param {{ css: string, node?: HTMLElement }[]} cssArr
  3170. */
  3171. let addStyles = (function () {
  3172. /** @type {string[]} */
  3173. const addedStyleIds = [];
  3174.  
  3175. /**
  3176. *
  3177. *
  3178. * @author Michael Barros <michaelcbarros@gmail.com>
  3179. * @param {{ css: string, node?: HTMLElement }[]} cssArr
  3180. * @param {{ useGM: boolean }} cssArr { useGM = true } = {}
  3181. */
  3182. function _addStyles(cssArr, { useGM = true } = {}) {
  3183. /**
  3184. *
  3185. *
  3186. * @author Michael Barros <michaelcbarros@gmail.com>
  3187. * @param {string} css
  3188. * @returns {HTMLStyleElement}
  3189. */
  3190. function createStyleElementFromCss(css) {
  3191. let style = document.createElement('style');
  3192.  
  3193. style.innerHTML = css.trim();
  3194.  
  3195. return style;
  3196. }
  3197.  
  3198. function removeStyleElements() {
  3199. for (let i = addedStyleIds.length - 1; i >= 0; i--) {
  3200. /** @type {HTMLStyleElement} */
  3201. let styleElem = document.getElementById(addedStyleIds[i]);
  3202.  
  3203. if (styleElem) {
  3204. styleElem.remove();
  3205.  
  3206. addedStyleIds.splice(i, 1);
  3207. }
  3208. }
  3209. }
  3210.  
  3211. function addStyleElements() {
  3212. for (let i = 0; i < cssArr.length; i++) {
  3213. try {
  3214. const css = cssArr[i].css;
  3215. const node = cssArr[i].node || document.head;
  3216.  
  3217. /** @type {HTMLStyleElement} */
  3218. let elem = useGM ? GM_addStyle(css) : createStyleElementFromCss(css);
  3219.  
  3220. elem.id = `akkd-custom-style-${(i + 1).toString().padStart(2, '0')}`;
  3221.  
  3222. node.append(elem);
  3223.  
  3224. addedStyleIds.push(elem.id);
  3225. } catch (error) {
  3226. console.error(error);
  3227. }
  3228. }
  3229. }
  3230.  
  3231. removeStyleElements();
  3232. addStyleElements();
  3233.  
  3234. return addedStyleIds;
  3235. }
  3236.  
  3237. return _addStyles;
  3238. })();
  3239.  
  3240. /**
  3241. * Return uuid of form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
  3242. *
  3243. * @author Michael Barros <michaelcbarros@gmail.com>
  3244. * @returns {string}
  3245. */
  3246. function uuid4() {
  3247. let uuid = '';
  3248. let ii;
  3249.  
  3250. for (ii = 0; ii < 32; ii += 1) {
  3251. switch (ii) {
  3252. case 8:
  3253. case 20:
  3254. uuid += '-';
  3255. uuid += ((Math.random() * 16) | 0).toString(16);
  3256.  
  3257. break;
  3258.  
  3259. case 12:
  3260. uuid += '-';
  3261. uuid += '4';
  3262.  
  3263. break;
  3264.  
  3265. case 16:
  3266. uuid += '-';
  3267. uuid += ((Math.random() * 4) | 8).toString(16);
  3268.  
  3269. break;
  3270.  
  3271. default:
  3272. uuid += ((Math.random() * 16) | 0).toString(16);
  3273. }
  3274. }
  3275.  
  3276. return uuid;
  3277. }
  3278.  
  3279. /**
  3280. *
  3281. *
  3282. * @author Michael Barros <michaelcbarros@gmail.com>
  3283. * @param {{} | []} obj
  3284. * @param {string | {oldName: string, newName: string}[]} oldName
  3285. * @param {string=} newName
  3286. * @returns
  3287. */
  3288. function renameProperty(obj, oldName, newName) {
  3289. function _renameProperty(obj, oldName, newName) {
  3290. let keys = Object.keys(obj);
  3291.  
  3292. for (let i = 0; i < keys.length; i++) {
  3293. let key = keys[i];
  3294. let value = obj[key];
  3295.  
  3296. if (value && typeof value == 'object') {
  3297. obj[key] = _renameProperty(value, oldName, newName);
  3298. }
  3299.  
  3300. if (obj.hasOwnProperty(oldName)) {
  3301. obj[newName] = obj[oldName];
  3302.  
  3303. delete obj[oldName];
  3304. }
  3305. }
  3306.  
  3307. return obj;
  3308. }
  3309.  
  3310. let renames = Array.isArray(oldName) ? oldName : [{ oldName, newName }];
  3311.  
  3312. for (let i = 0; i < renames.length; i++) {
  3313. const rename = renames[i];
  3314.  
  3315. obj = _renameProperty(obj, rename.oldName, rename.newName);
  3316. }
  3317.  
  3318. return obj;
  3319. }
  3320.  
  3321. /**
  3322. *
  3323. *
  3324. * @author Michael Barros <michaelcbarros@gmail.com>
  3325. * @param {Promise<T>} fn
  3326. * @param {{ retries?: number; interval?: number; maxTime?: number; throwError?: boolean; }} { retries = 3, interval = 100, maxTime = null, throwError = false }
  3327. * @returns {Promise<T>}
  3328. * @template T
  3329. */
  3330. async function retry(fn, { retries = 3, interval = 100, maxTime = null, throwError = false }) {
  3331. let start = new Date();
  3332. let timeLapsed;
  3333.  
  3334. async function _retry() {
  3335. try {
  3336. return await fn;
  3337. } catch (error) {
  3338. timeLapsed = new Date() - start;
  3339.  
  3340. await wait(interval);
  3341.  
  3342. if (maxTime) {
  3343. if (timeLapsed >= maxTime) {
  3344. if (throwError) {
  3345. throw error;
  3346. } else {
  3347. return null;
  3348. }
  3349. }
  3350. } else {
  3351. --retries;
  3352.  
  3353. if (retries === 0) {
  3354. if (throwError) {
  3355. throw error;
  3356. } else {
  3357. return null;
  3358. }
  3359. }
  3360. }
  3361.  
  3362. return await _retry();
  3363. }
  3364. }
  3365.  
  3366. return await _retry();
  3367. }
  3368.  
  3369. /**
  3370. *
  3371. *
  3372. * @author Michael Barros <michaelcbarros@gmail.com>
  3373. * @param {string} htmlStr
  3374. * @returns {NodeListOf<ChildNode>}
  3375. */
  3376. function createElementsFromHTML(htmlStr) {
  3377. let div = document.createElement('div');
  3378.  
  3379. div.innerHTML = htmlStr.trim();
  3380.  
  3381. return div.childNodes;
  3382. }
  3383.  
  3384. /**
  3385. * Checks if a variable is a number.
  3386. *
  3387. * @param {*} variable - The variable to check.
  3388. * @returns {boolean} - `true` if the variable is a number or a number represented as a string, `false` otherwise.
  3389. */
  3390. function isNumber(variable) {
  3391. return (typeof variable == 'string' || typeof variable == 'number') && !isNaN(variable - 0) && variable !== '';
  3392. }
  3393.  
  3394. /**
  3395. *
  3396. *
  3397. * @author Michael Barros <michaelcbarros@gmail.com>
  3398. * @param {string | number} val
  3399. * @returns
  3400. */
  3401. function parseNumberSafe(val) {
  3402. if (isNumber(val)) {
  3403. val = parseFloat(val);
  3404. }
  3405.  
  3406. return val;
  3407. }
  3408.  
  3409. /**
  3410. *
  3411. *
  3412. * @author Michael Barros <michaelcbarros@gmail.com>
  3413. * @param {number} num
  3414. * @returns {boolean}
  3415. */
  3416. function isInt(num) {
  3417. return Number(num) === num && num % 1 === 0;
  3418. }
  3419.  
  3420. /**
  3421. *
  3422. *
  3423. * @author Michael Barros <michaelcbarros@gmail.com>
  3424. * @param {number} num
  3425. * @returns {boolean}
  3426. */
  3427. function isFloat(num) {
  3428. return Number(num) === num && num % 1 !== 0;
  3429. }
  3430.  
  3431. /*
  3432. *
  3433. *
  3434. * @author Michael Barros <michaelcbarros@gmail.com>
  3435. * @param {HTMLElement} elem
  3436. * @param {string} prop
  3437. * @param {Window=} _window
  3438. * @returns {string | number | null}
  3439. */
  3440. /**
  3441. *
  3442. *
  3443. * @author Michael Barros <michaelcbarros@gmail.com>
  3444. * @param {HTMLElement} elem
  3445. * @param {string} prop
  3446. * @param {Window} [_window=getWindow()]
  3447. * @returns {string | number | null}
  3448. */
  3449. function getStyle(elem, prop, _window = null) {
  3450. _window = _window || getWindow();
  3451.  
  3452. let value = parseNumberSafe(
  3453. window
  3454. .getComputedStyle(elem, null)
  3455. .getPropertyValue(prop)
  3456. .replace(/^(\d+)px$/, '$1')
  3457. );
  3458.  
  3459. return value;
  3460. }
  3461.  
  3462. /**
  3463. *
  3464. *
  3465. * @author Michael Barros <michaelcbarros@gmail.com>
  3466. * @param {HTMLElement} beforeElem
  3467. * @param {HTMLElement=} afterElem
  3468. */
  3469. function attachHorizontalResizer(beforeElem, afterElem) {
  3470. let resizer = document.createElement('span');
  3471.  
  3472. resizer.className = 'akkd-horz-resizer';
  3473.  
  3474. beforeElem.after(resizer);
  3475.  
  3476. afterElem = afterElem ? afterElem : resizer.nextElementSibling;
  3477.  
  3478. // resizer.addEventListener('mousedown', init, false);
  3479.  
  3480. // /**
  3481. // *
  3482. // *
  3483. // * @author Michael Barros <michaelcbarros@gmail.com>
  3484. // * @param {MouseEvent} ev
  3485. // */
  3486. // function init(ev) {
  3487. // getWindow().addEventListener('mousemove', resize, false);
  3488. // getWindow().addEventListener('mouseup', stopResize, false);
  3489. // }
  3490.  
  3491. // /**
  3492. // *
  3493. // *
  3494. // * @author Michael Barros <michaelcbarros@gmail.com>
  3495. // * @param {MouseEvent} ev
  3496. // */
  3497. // function resize(ev) {
  3498. // beforeElem.style.height = `${ev.clientY - beforeElem.offsetTop}px`;
  3499. // }
  3500.  
  3501. // /**
  3502. // *
  3503. // *
  3504. // * @author Michael Barros <michaelcbarros@gmail.com>
  3505. // * @param {MouseEvent} ev
  3506. // */
  3507. // function stopResize(ev) {
  3508. // getWindow().removeEventListener('mousemove', resize, false);
  3509. // getWindow().removeEventListener('mouseup', stopResize, false);
  3510. // }
  3511.  
  3512. let prevX = -1;
  3513. let prevY = -1;
  3514. let dir = null;
  3515.  
  3516. $(resizer).on('mousedown', function (e) {
  3517. prevX = e.clientX;
  3518. prevY = e.clientY;
  3519. dir = 'n'; // $(this).attr('id');
  3520.  
  3521. $(document).on('mousemove', resize);
  3522. $(document).on('mouseup', stopResize);
  3523. });
  3524.  
  3525. /**
  3526. *
  3527. *
  3528. * @author Michael Barros <michaelcbarros@gmail.com>
  3529. * @param {JQuery.MouseMoveEvent<Document, undefined, Document, Document>} ev
  3530. */
  3531. function resize(ev) {
  3532. if (prevX == -1) return;
  3533.  
  3534. let boxX = $(afterElem).position().left;
  3535. let boxY = $(afterElem).position().top;
  3536. let boxW = $(afterElem).width();
  3537. let boxH = $(afterElem).height();
  3538.  
  3539. let dx = ev.clientX - prevX;
  3540. let dy = ev.clientY - prevY;
  3541.  
  3542. switch (dir) {
  3543. case 'n':
  3544. // north
  3545. boxY += dy;
  3546. boxH -= dy;
  3547.  
  3548. break;
  3549.  
  3550. case 's':
  3551. // south
  3552. boxH += dy;
  3553.  
  3554. break;
  3555.  
  3556. case 'w':
  3557. // west
  3558. boxX += dx;
  3559. boxW -= dx;
  3560.  
  3561. break;
  3562.  
  3563. case 'e':
  3564. // east
  3565. boxW += dx;
  3566.  
  3567. break;
  3568.  
  3569. default:
  3570. break;
  3571. }
  3572.  
  3573. $(afterElem).css({
  3574. // top: boxY + 'px',
  3575. // left: boxX + 'px',
  3576. // width: boxW + 'px',
  3577. height: boxH + 'px',
  3578. });
  3579.  
  3580. let lines = [
  3581. //
  3582. // ['newHeight', newHeight],
  3583. ['clientY', ev.clientY],
  3584. ['beforeElem.top', roundNumber($(beforeElem).position().top)],
  3585. ['beforeElem.height', $(beforeElem).height()],
  3586. '',
  3587. ['afterElem.top', roundNumber($(afterElem).position().top)],
  3588. ['afterElem.height', $(afterElem).height()],
  3589. ];
  3590.  
  3591. // writeDebugMsg(lines);
  3592. console.debug([`y: ${ev.clientY}`, `b.top: ${roundNumber($(beforeElem).position().top)}`, `b.height: ${$(beforeElem).height()}`, `a.top: ${roundNumber($(afterElem).position().top)}`, `a.height: ${$(afterElem).height()}`].join(' '));
  3593.  
  3594. function writeDebugMsg(lines) {
  3595. let outputLines = ['*'.repeat(60)];
  3596.  
  3597. let tags = lines.map((line) => (Array.isArray(line) ? line[0] : line));
  3598.  
  3599. lines.forEach((line) => {
  3600. if (Array.isArray(line)) {
  3601. // need to require lpad-align
  3602. // outputLines.push(`${lpadAlign(line[0], tags)}: ${line[1]}`);
  3603. } else {
  3604. outputLines.push(line);
  3605. }
  3606. });
  3607.  
  3608. outputLines.push('*'.repeat(60));
  3609.  
  3610. console.debug(outputLines.join('\n'));
  3611. }
  3612.  
  3613. function roundNumber(num, places = 2) {
  3614. return parseFloat(parseFloat(num.toString()).toFixed(places));
  3615. // Math.round(num * 100) / 100
  3616. }
  3617.  
  3618. prevX = ev.clientX;
  3619. prevY = ev.clientY;
  3620. }
  3621.  
  3622. /**
  3623. *
  3624. *
  3625. * @author Michael Barros <michaelcbarros@gmail.com>
  3626. * @param {JQuery.MouseMoveEvent<Document, undefined, Document, Document>} ev
  3627. */
  3628. function stopResize(ev) {
  3629. prevX = -1;
  3630. prevY = -1;
  3631.  
  3632. $(document).off('mousemove', resize);
  3633. $(document).off('mouseup', stopResize);
  3634. }
  3635. }
  3636.  
  3637. function traceMethodCalls(obj) {
  3638. /** @type {ProxyHandler} */
  3639. let handler = {
  3640. get(target, propKey, receiver) {
  3641. if (propKey == 'isProxy') return true;
  3642.  
  3643. const prop = target[propKey];
  3644.  
  3645. if (typeof prop == 'undefined') return;
  3646.  
  3647. if (typeof prop === 'object' && target[propKey] !== null) {
  3648. if (!prop.isProxy) {
  3649. target[propKey] = new Proxy(prop, handler);
  3650.  
  3651. return target[propKey];
  3652. } else {
  3653. return target[propKey];
  3654. }
  3655. }
  3656.  
  3657. if (typeof target[propKey] == 'function') {
  3658. const origMethod = target[propKey];
  3659.  
  3660. return function (...args) {
  3661. let result = origMethod.apply(this, args);
  3662.  
  3663. console.log(propKey + JSON.stringify(args) + ' -> ' + JSON.stringify(result));
  3664.  
  3665. return result;
  3666. };
  3667. } else {
  3668. return target[propKey];
  3669. }
  3670. },
  3671. };
  3672.  
  3673. return new Proxy(obj, handler);
  3674. }
  3675.  
  3676. /**
  3677. *
  3678. *
  3679. * @author Michael Barros <michaelcbarros@gmail.com>
  3680. * @param {HTMLElement} elem
  3681. * @param {number} [topOffset=0]
  3682. * @returns {boolean}
  3683. */
  3684. function isVisible(elem, topOffset = 0) {
  3685. /**
  3686. * Checks if a DOM element is visible. Takes into
  3687. * consideration its parents and overflow.
  3688. *
  3689. * @param {HTMLElement} el the DOM element to check if is visible
  3690. *
  3691. * These params are optional that are sent in recursively,
  3692. * you typically won't use these:
  3693. *
  3694. * @param {number} top Top corner position number
  3695. * @param {number} right Right corner position number
  3696. * @param {number} bottom Bottom corner position number
  3697. * @param {number} left Left corner position number
  3698. * @param {number} width Element width number
  3699. * @param {number} height Element height number
  3700. * @returns {boolean}
  3701. */
  3702. function _isVisible(el, top, right, bottom, left, width, height) {
  3703. let parent = el.parentNode;
  3704. let VISIBLE_PADDING = 2;
  3705.  
  3706. if (!_elementInDocument(el)) {
  3707. return false;
  3708. }
  3709.  
  3710. // Return true for document node
  3711. if (9 === parent.nodeType) {
  3712. return true;
  3713. }
  3714.  
  3715. // Return false if our element is invisible
  3716. if ('0' === _getStyle(el, 'opacity') || 'none' === _getStyle(el, 'display') || 'hidden' === _getStyle(el, 'visibility')) {
  3717. return false;
  3718. }
  3719.  
  3720. if ('undefined' === typeof top || 'undefined' === typeof right || 'undefined' === typeof bottom || 'undefined' === typeof left || 'undefined' === typeof width || 'undefined' === typeof height) {
  3721. top = el.offsetTop + topOffset;
  3722. left = el.offsetLeft;
  3723. bottom = top + el.offsetHeight;
  3724. right = left + el.offsetWidth;
  3725. width = el.offsetWidth;
  3726. height = el.offsetHeight;
  3727. }
  3728.  
  3729. // If we have a parent, let's continue:
  3730. if (parent) {
  3731. // Check if the parent can hide its children.
  3732. if ('hidden' === _getStyle(parent, 'overflow') || 'scroll' === _getStyle(parent, 'overflow')) {
  3733. // Only check if the offset is different for the parent
  3734. if (
  3735. // If the target element is to the right of the parent elm
  3736. left + VISIBLE_PADDING > parent.offsetWidth + parent.scrollLeft ||
  3737. // If the target element is to the left of the parent elm
  3738. left + width - VISIBLE_PADDING < parent.scrollLeft ||
  3739. // If the target element is under the parent elm
  3740. top + VISIBLE_PADDING > parent.offsetHeight + parent.scrollTop ||
  3741. // If the target element is above the parent elm
  3742. top + height - VISIBLE_PADDING < parent.scrollTop
  3743. ) {
  3744. // Our target element is out of bounds:
  3745. return false;
  3746. }
  3747. }
  3748. // Add the offset parent's left/top coords to our element's offset:
  3749. if (el.offsetParent === parent) {
  3750. left += parent.offsetLeft;
  3751. top += parent.offsetTop;
  3752. }
  3753. // Let's recursively check upwards:
  3754. return _isVisible(parent, top, right, bottom, left, width, height);
  3755. }
  3756.  
  3757. return true;
  3758. }
  3759.  
  3760. // Cross browser method to get style properties:
  3761. /**
  3762. *
  3763. *
  3764. * @author Michael Barros <michaelcbarros@gmail.com>
  3765. * @param {HTMLElement} el
  3766. * @param {string} property
  3767. * @returns
  3768. */
  3769. function _getStyle(el, property) {
  3770. let value;
  3771.  
  3772. if (window.getComputedStyle) {
  3773. value = document.defaultView.getComputedStyle(el, null)[property];
  3774. }
  3775.  
  3776. if (el.currentStyle) {
  3777. value = el.currentStyle[property];
  3778. }
  3779.  
  3780. return value;
  3781. }
  3782.  
  3783. /**
  3784. *
  3785. *
  3786. * @author Michael Barros <michaelcbarros@gmail.com>
  3787. * @param {HTMLElement} element
  3788. * @returns {boolean}
  3789. */
  3790. function _elementInDocument(element) {
  3791. while ((element = element.parentNode)) {
  3792. if (element == document) {
  3793. return true;
  3794. }
  3795. }
  3796.  
  3797. return false;
  3798. }
  3799.  
  3800. return _isVisible(elem);
  3801. }
  3802.  
  3803. /**
  3804. * @summary
  3805. * High-order function that memoizes a function, by creating a scope
  3806. * to store the result of each function call, returning the cached
  3807. * result when the same inputs is given.
  3808. *
  3809. * @description
  3810. * Memoization is an optimization technique used primarily to speed up
  3811. * functions by storing the results of expensive function calls, and returning
  3812. * the cached result when the same inputs occur again.
  3813. *
  3814. * Each time a memoized function is called, its parameters are used as keys to index the cache.
  3815. * If the index (key) is present, then it can be returned, without executing the entire function.
  3816. * If the index is not cached, then all the body of the function is executed, and the result is
  3817. * added to the cache.
  3818. *
  3819. * @see https://www.sitepoint.com/implementing-memoization-in-javascript/
  3820. *
  3821. * @export
  3822. * @param {Function} func: function to memoize
  3823. * @returns {Function}
  3824. */
  3825. function memoize(func) {
  3826. const cache = {};
  3827.  
  3828. function memoized(...args) {
  3829. const key = JSON.stringify(args);
  3830.  
  3831. if (key in cache) return cache[key];
  3832.  
  3833. if (globalThis instanceof this.constructor) {
  3834. return (cache[key] = func.apply(null, args));
  3835. } else {
  3836. return (cache[key] = func.apply(this, args));
  3837. }
  3838. }
  3839.  
  3840. memoized.toString = () => func.toString();
  3841.  
  3842. return memoized;
  3843. }
  3844.  
  3845. function memoizeClass(clazz, options = { toIgnore: [] }) {
  3846. let funcs = getObjProps(clazz, { namesOnly: true }).funcs;
  3847.  
  3848. for (let i = 0; i < funcs.length; i++) {
  3849. let funcName = funcs[i];
  3850.  
  3851. if (options.toIgnore.includes(funcName)) continue;
  3852.  
  3853. let func = Object.getOwnPropertyDescriptor(clazz.prototype, funcName);
  3854.  
  3855. let memFunc = memoize(func.value);
  3856.  
  3857. Object.defineProperty(clazz.prototype, funcName, {
  3858. get: function () {
  3859. return memFunc;
  3860. },
  3861. });
  3862. }
  3863.  
  3864. let props = getObjProps(clazz, { namesOnly: true }).props;
  3865.  
  3866. for (let i = 0; i < props.length; i++) {
  3867. let propName = props[i];
  3868.  
  3869. if (options.toIgnore.includes(propName)) continue;
  3870.  
  3871. let prop = Object.getOwnPropertyDescriptor(clazz.prototype, propName);
  3872. let cacheKey = `_${propName}-cache_`;
  3873.  
  3874. Object.defineProperty(clazz.prototype, propName, {
  3875. get: function () {
  3876. return (this[cacheKey] = this[cacheKey] || prop.get.call(this));
  3877. },
  3878. });
  3879. }
  3880. }
  3881.  
  3882. /**
  3883. *
  3884. *
  3885. * @author Michael Barros <michaelcbarros@gmail.com>
  3886. * @param {any[]} objects
  3887. * @returns {object}
  3888. */
  3889. function merge(...objects) {
  3890. const isObject = (obj) => Object.prototype.toString.call(obj) == '[object Object]' && obj.constructor && obj.constructor.name == 'Object';
  3891.  
  3892. let _merge = (_target, _source, _isMergingArrays) => {
  3893. if (!isObject(_target) || !isObject(_source)) {
  3894. return _source;
  3895. }
  3896.  
  3897. Object.keys(_source).forEach((key) => {
  3898. const targetValue = _target[key];
  3899. const sourceValue = _source[key];
  3900.  
  3901. if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
  3902. if (_isMergingArrays) {
  3903. _target[key] = targetValue.map((x, i) => (sourceValue.length <= i ? x : _merge(x, sourceValue[i], _isMergingArrays)));
  3904.  
  3905. if (sourceValue.length > targetValue.length) {
  3906. _target[key] = _target[key].concat(sourceValue.slice(targetValue.length));
  3907. }
  3908. } else {
  3909. _target[key] = targetValue.concat(sourceValue);
  3910. }
  3911. } else if (isObject(targetValue) && isObject(sourceValue)) {
  3912. _target[key] = _merge(Object.assign({}, targetValue), sourceValue, _isMergingArrays);
  3913. } else {
  3914. _target[key] = sourceValue;
  3915. }
  3916. });
  3917.  
  3918. return _target;
  3919. };
  3920.  
  3921. const isMergingArrays = typeof objects[objects.length - 1] == 'boolean' ? objects.pop() : false;
  3922.  
  3923. if (objects.length < 2) throw new Error('mergeEx: this function expects at least 2 objects to be provided');
  3924.  
  3925. if (objects.some((object) => !isObject(object))) throw new Error('mergeEx: all values should be of type "object"');
  3926.  
  3927. const target = objects.shift();
  3928. let source;
  3929.  
  3930. while ((source = objects.shift())) {
  3931. _merge(target, source, isMergingArrays);
  3932. }
  3933.  
  3934. return target;
  3935. }
  3936.  
  3937. /**
  3938. *
  3939. *
  3940. * @author Michael Barros <michaelcbarros@gmail.com>
  3941. * @param {Window} [windowOrFrame=getTopWindow()]
  3942. * @param {Window[]} [allFrameArray=[]]
  3943. * @returns {Window[]}
  3944. */
  3945. function getAllFrames(windowOrFrame = getTopWindow(), allFrameArray = []) {
  3946. allFrameArray.push(windowOrFrame.frames);
  3947.  
  3948. for (var i = 0; i < windowOrFrame.frames.length; i++) {
  3949. getAllFrames(windowOrFrame.frames[i], allFrameArray);
  3950. }
  3951.  
  3952. return allFrameArray;
  3953. }
  3954.  
  3955. /**
  3956. *
  3957. *
  3958. * @author Michael Barros <michaelcbarros@gmail.com>
  3959. * @returns {boolean}
  3960. */
  3961. function windowIsFocused() {
  3962. let frames = getAllFrames();
  3963.  
  3964. for (let i = 0; i < frames.length; i++) {
  3965. const frame = frames[i];
  3966.  
  3967. try {
  3968. if (frame.document.hasFocus()) {
  3969. return true;
  3970. }
  3971. } catch (error) {}
  3972. }
  3973.  
  3974. return false;
  3975. }
  3976.  
  3977. function setupWindowHasFocused() {
  3978. let frames = getAllFrames();
  3979.  
  3980. for (let i = 0; i < frames.length; i++) {
  3981. const frame = frames[i];
  3982.  
  3983. try {
  3984. frame.hasFocus = windowIsFocused;
  3985. } catch (error) {}
  3986. }
  3987. }
  3988.  
  3989. /**
  3990. *
  3991. *
  3992. * @author Michael Barros <michaelcbarros@gmail.com>
  3993. * @param {[]} array
  3994. * @param {any} element
  3995. * @param {number} index
  3996. */
  3997. function moveArrayElement(array, filter, index) {
  3998. let item = array.filter((item) => item === filter)[0];
  3999.  
  4000. if (item) {
  4001. array = array.filter((item) => item !== filter);
  4002.  
  4003. array.unshift(item);
  4004. }
  4005.  
  4006. return array;
  4007. }
  4008.  
  4009. /**
  4010. *
  4011. *
  4012. * @author Michael Barros <michaelcbarros@gmail.com>
  4013. * @param {HTMLElement} elem
  4014. * @param {(elem: HTMLElement, level: number) => void} callback
  4015. * @param {number} [level=0]
  4016. */
  4017. function walkDom(elem, callback, level = 0) {
  4018. let children = elem.children;
  4019.  
  4020. callback(elem, level);
  4021.  
  4022. for (let i = 0; i < children.length; i++) {
  4023. /** @type {HTMLElement} */
  4024. let child = children[i];
  4025.  
  4026. walkDom(child, callback, level + 1);
  4027.  
  4028. if (child.shadowRoot) {
  4029. walkDom(child.shadowRoot, callback, level + 2);
  4030. }
  4031. }
  4032. }
  4033.  
  4034. /**
  4035. *
  4036. *
  4037. * @author Michael Barros <michaelcbarros@gmail.com>
  4038. * @param {T} obj
  4039. * @param {(key: string | number, value: any, keyPath: string, callbackRes: { doBreak: boolean, returnValue: any | null }, obj: T) => boolean} callback
  4040. * @template T
  4041. * @returns {{ dottedObj: T, returnValue: any }}
  4042. */
  4043. function walkObj(obj, callback) {
  4044. let callbackRes = {
  4045. doBreak: false,
  4046. returnValue: null,
  4047. };
  4048.  
  4049. /**
  4050. *
  4051. *
  4052. * @author Michael Barros <michaelcbarros@gmail.com>
  4053. * @param {{}} _obj
  4054. * @param {string[]} keyPath
  4055. * @param {{}} newObj
  4056. */
  4057. function _walk(_obj, keyPath, newObj) {
  4058. keyPath = typeof keyPath === 'undefined' ? [] : keyPath;
  4059. newObj = typeof newObj === 'undefined' ? {} : newObj;
  4060.  
  4061. for (let key in _obj) {
  4062. if (_obj.hasOwnProperty(key)) {
  4063. let value = _obj[key];
  4064.  
  4065. keyPath.push(key);
  4066.  
  4067. callback.apply(this, [key, value, keyPath.join('.'), callbackRes, obj]);
  4068.  
  4069. if (typeof value === 'object' && value !== null) {
  4070. newObj = _walk(value, keyPath, newObj);
  4071. } else {
  4072. let newKey = keyPath.join('.');
  4073.  
  4074. newObj[newKey] = value;
  4075. }
  4076.  
  4077. keyPath.pop();
  4078.  
  4079. if (callbackRes.doBreak) {
  4080. break;
  4081. }
  4082. }
  4083. }
  4084.  
  4085. return newObj;
  4086. }
  4087.  
  4088. let newObj = _walk(obj);
  4089.  
  4090. return {
  4091. dottedObj: newObj,
  4092. returnValue: callbackRes.returnValue,
  4093. };
  4094. }
  4095.  
  4096. /**
  4097. * A function to take a string written in dot notation style, and use it to
  4098. * find a nested object property inside of an object.
  4099. *
  4100. * Useful in a plugin or module that accepts a JSON array of objects, but
  4101. * you want to let the user specify where to find various bits of data
  4102. * inside of each custom object instead of forcing a standardized
  4103. * property list.
  4104. *
  4105. * @author Michael Barros <michaelcbarros@gmail.com>
  4106. * @param {{}} obj
  4107. * @param {string} dotPath
  4108. * @returns {*}
  4109. */
  4110. function getNestedDot(obj, dotPath) {
  4111. let parts = dotPath.split('.');
  4112. let length = parts.length;
  4113. let property = obj || this;
  4114.  
  4115. for (let i = 0; i < length; i++) {
  4116. property = property[parts[i]];
  4117. }
  4118.  
  4119. return property;
  4120. }
  4121.  
  4122. /**
  4123. *
  4124. *
  4125. * @author Michael Barros <michaelcbarros@gmail.com>
  4126. * @param {{}} obj
  4127. * @param {number} maxLevel
  4128. */
  4129. function getDottedObj(obj, maxLevel = 50) {
  4130. /**
  4131. *
  4132. *
  4133. * @author Michael Barros <michaelcbarros@gmail.com>
  4134. * @param {{}} _obj
  4135. * @param {string[]} keyPath
  4136. * @param {{}} newObj
  4137. * @param {number} level
  4138. */
  4139. function _worker(_obj, keyPath, newObj, level = 0) {
  4140. keyPath = typeof keyPath === 'undefined' ? [] : keyPath;
  4141. newObj = typeof newObj === 'undefined' ? {} : newObj;
  4142.  
  4143. for (let key in _obj) {
  4144. if (_obj.hasOwnProperty(key)) {
  4145. let value = _obj[key];
  4146.  
  4147. keyPath.push(key);
  4148.  
  4149. if (typeof value === 'object' && value !== null) {
  4150. newObj = _worker(value, keyPath, newObj, level++);
  4151. } else {
  4152. let newKey = keyPath.join('.');
  4153.  
  4154. newObj[newKey] = value;
  4155. }
  4156.  
  4157. keyPath.pop();
  4158.  
  4159. if (maxLevel > 0 && level >= maxLevel) {
  4160. break;
  4161. }
  4162. }
  4163. }
  4164.  
  4165. return newObj;
  4166. }
  4167.  
  4168. let dottedObj = _worker(obj);
  4169.  
  4170. return dottedObj;
  4171. }
  4172.  
  4173. function isNode() {
  4174. return !(typeof window !== 'undefined' && typeof window.document !== 'undefined');
  4175. }
  4176.  
  4177. /**
  4178. *
  4179. *
  4180. * @author Michael Barros <michaelcbarros@gmail.com>
  4181. * @returns {number}
  4182. */
  4183. function getCurrentTimeMs() {
  4184. if (isNode()) {
  4185. const NS_PER_MS = 1e6;
  4186.  
  4187. let time = process.hrtime();
  4188.  
  4189. return time[0] * 1000 + time[1] / NS_PER_MS;
  4190. } else {
  4191. return performance.now();
  4192. }
  4193. }
  4194.  
  4195. const intervalIDsMap = new Map();
  4196. const timeoutIDsMap = new Map();
  4197.  
  4198. /**
  4199. * Schedules the repeated execution of a function (callback) with a fixed time delay between each call.
  4200. * @param {TimerHandler} handler - A function to be executed repeatedly.
  4201. * @param {number} [timeout] - The time, in milliseconds, between each function call. Default is 0.
  4202. * @param {...any} [args] - Additional arguments to be passed to the function.
  4203. * @returns {number} - An identifier representing the interval. This value can be used with clearInterval to cancel the interval.
  4204. */
  4205. function setIntervalEx(handler, timeout, ...args) {
  4206. if (isNode()) {
  4207. return setInterval(handler, timeout, ...args);
  4208. } else {
  4209. let startTime = getCurrentTimeMs();
  4210. let elapsedTime = 0;
  4211. /** @type {number} */
  4212. let intervalId;
  4213. let intervalIdTemp;
  4214.  
  4215. function loop(currentTime) {
  4216. if (intervalIDsMap.get(intervalId)) {
  4217. const deltaTime = currentTime - startTime;
  4218.  
  4219. elapsedTime += deltaTime;
  4220.  
  4221. if (elapsedTime >= timeout) {
  4222. handler(...args);
  4223.  
  4224. elapsedTime = 0;
  4225. }
  4226.  
  4227. startTime = currentTime;
  4228.  
  4229. intervalIdTemp = window.requestAnimationFrame(loop);
  4230. } else {
  4231. window.cancelAnimationFrame(intervalIdTemp);
  4232. intervalIDsMap.delete(intervalId);
  4233. }
  4234. }
  4235.  
  4236. intervalId = window.requestAnimationFrame(loop);
  4237.  
  4238. intervalIDsMap.set(intervalId, true);
  4239.  
  4240. return intervalId;
  4241. }
  4242. }
  4243.  
  4244. /**
  4245. *
  4246. *
  4247. * @author Michael Barros <michaelcbarros@gmail.com>
  4248. * @param {number} intervalId
  4249. */
  4250. function clearIntervalEx(intervalId) {
  4251. if (isNode()) {
  4252. clearInterval(intervalId);
  4253. } else {
  4254. intervalIDsMap.set(intervalId, false);
  4255.  
  4256. window.cancelAnimationFrame(intervalId);
  4257. }
  4258. }
  4259.  
  4260. /**
  4261. * Schedules the execution of a function (callback) after a specified time delay.
  4262. * @param {TimerHandler} handler - A function to be executed.
  4263. * @param {number} [timeout] - The time, in milliseconds, to wait before executing the function. Default is 0.
  4264. * @param {...any} [args] - Additional arguments to be passed to the function.
  4265. * @returns {number} - An identifier representing the timeout. This value can be used with clearTimeout to cancel the timeout.
  4266. */
  4267. function setTimeoutEx(handler, timeout, ...args) {
  4268. if (isNode()) {
  4269. return setTimeout(handler, timeout, ...args);
  4270. } else {
  4271. let startTime = getCurrentTimeMs();
  4272. /** @type {number} */
  4273. let timeoutId;
  4274. let timeoutIdTemp;
  4275.  
  4276. function loop(currentTime) {
  4277. if (timeoutIDsMap.get(timeoutId)) {
  4278. const deltaTime = currentTime - startTime;
  4279.  
  4280. if (deltaTime >= timeout) {
  4281. handler(...args);
  4282. } else {
  4283. timeoutIdTemp = window.requestAnimationFrame(loop);
  4284. }
  4285. } else {
  4286. window.cancelAnimationFrame(timeoutIdTemp);
  4287. timeoutIDsMap.delete(timeoutId);
  4288. }
  4289. }
  4290.  
  4291. timeoutId = window.requestAnimationFrame(loop);
  4292.  
  4293. timeoutIDsMap.set(timeoutId, true);
  4294.  
  4295. return timeoutId;
  4296. }
  4297. }
  4298.  
  4299. /**
  4300. *
  4301. *
  4302. * @author Michael Barros <michaelcbarros@gmail.com>
  4303. * @param {number} timeoutId
  4304. */
  4305. function clearTimeoutEx(timeoutId) {
  4306. if (isNode()) {
  4307. clearTimeout(timeoutId);
  4308. } else {
  4309. timeoutIDsMap.set(timeoutId, false);
  4310.  
  4311. window.cancelAnimationFrame(timeoutId);
  4312. }
  4313. }
  4314.  
  4315. /**
  4316. *
  4317. *
  4318. * @author Michael Barros <michaelcbarros@gmail.com>
  4319. * @param {string} attribute
  4320. * @param {string} value
  4321. * @param {string} elementType
  4322. * @returns {HTMLElement[]}
  4323. */
  4324. function findByAttributeValue(attribute, value, elementType) {
  4325. elementType = elementType || '*';
  4326.  
  4327. let all = document.getElementsByTagName(elementType);
  4328. let foundElements = [];
  4329.  
  4330. for (let i = 0; i < all.length; i++) {
  4331. if (all[i].getAttribute(attribute).includes(value)) {
  4332. foundElements.push(all[i]);
  4333. }
  4334. }
  4335.  
  4336. return foundElements;
  4337. }
  4338.  
  4339. /**
  4340. *
  4341. *
  4342. * @author Michael Barros <michaelcbarros@gmail.com>
  4343. * @returns {number}
  4344. */
  4345. function getLocalStorageSize() {
  4346. let total = 0;
  4347.  
  4348. for (let x in localStorage) {
  4349. // Value is multiplied by 2 due to data being stored in `utf-16` format, which requires twice the space.
  4350. let amount = localStorage[x].length * 2;
  4351.  
  4352. if (!isNaN(amount) && localStorage.hasOwnProperty(x)) {
  4353. total += amount;
  4354. }
  4355. }
  4356.  
  4357. return total;
  4358. }
  4359.  
  4360. /**
  4361. *
  4362. *
  4363. * @author Michael Barros <michaelcbarros@gmail.com>
  4364. * @param {number} bytes
  4365. * @param {boolean} [si=false]
  4366. * @returns {string}
  4367. */
  4368. function bytes2HumanReadable(bytes, si = false) {
  4369. let thresh = si ? 1000 : 1024;
  4370.  
  4371. if (Math.abs(bytes) < thresh) {
  4372. return bytes + ' B';
  4373. }
  4374.  
  4375. let units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
  4376. let u = -1;
  4377.  
  4378. while (true) {
  4379. bytes = bytes / thresh;
  4380. u++;
  4381.  
  4382. if (!(Math.abs(bytes) >= thresh && u < units.length - 1)) {
  4383. break;
  4384. }
  4385. }
  4386.  
  4387. return bytes.toFixed(1) + ' ' + units[u];
  4388. }
  4389.  
  4390. async function GM_fetch(url, fetchInit = {}) {
  4391. if (!window.GM_xmlhttpRequest) {
  4392. console.warn('GM_xmlhttpRequest not required. Using native fetch.');
  4393.  
  4394. return await fetch(url, fetchInit);
  4395. }
  4396.  
  4397. let parseHeaders = function (headersString) {
  4398. const headers = new Headers();
  4399.  
  4400. for (const line of headersString.trim().split('\n')) {
  4401. const [key, ...valueParts] = line.split(':');
  4402.  
  4403. if (key) {
  4404. headers.set(key.trim().toLowerCase(), valueParts.join(':').trim());
  4405. }
  4406. }
  4407.  
  4408. return headers;
  4409. };
  4410.  
  4411. const defaultFetchInit = { method: 'get' };
  4412. const { headers, method } = { ...defaultFetchInit, ...fetchInit };
  4413. const isStreamSupported = GM_xmlhttpRequest?.RESPONSE_TYPE_STREAM;
  4414. const HEADERS_RECEIVED = 2;
  4415.  
  4416. if (!isStreamSupported) {
  4417. return new Promise((resolve, _reject) => {
  4418. const blobPromise = new Promise((resolve, reject) => {
  4419. GM_xmlhttpRequest({
  4420. url,
  4421. method,
  4422. headers,
  4423. responseType: 'blob',
  4424. onload: async (response) => resolve(response.response),
  4425. onerror: reject,
  4426. onreadystatechange: onHeadersReceived,
  4427. });
  4428. });
  4429.  
  4430. blobPromise.catch(_reject);
  4431.  
  4432. function onHeadersReceived(gmResponse) {
  4433. const { readyState, responseHeaders, status, statusText } = gmResponse;
  4434.  
  4435. if (readyState === HEADERS_RECEIVED) {
  4436. const headers = parseHeaders(responseHeaders);
  4437.  
  4438. resolve({
  4439. headers,
  4440. status,
  4441. statusText,
  4442. arrayBuffer: () => blobPromise.then((blob) => blob.arrayBuffer()),
  4443. blob: () => blobPromise,
  4444. json: () => blobPromise.then((blob) => blob.text()).then((text) => JSON.parse(text)),
  4445. text: () => blobPromise.then((blob) => blob.text()),
  4446. });
  4447. }
  4448. }
  4449. });
  4450. } else {
  4451. return new Promise((resolve, _reject) => {
  4452. const responsePromise = new Promise((resolve, reject) => {
  4453. void GM_xmlhttpRequest({
  4454. url,
  4455. method,
  4456. headers,
  4457. responseType: 'stream',
  4458. onerror: reject,
  4459. onreadystatechange: onHeadersReceived,
  4460. // onloadstart: (gmResponse) => logDebug('[onloadstart]', gmResponse), // debug
  4461. });
  4462. });
  4463.  
  4464. responsePromise.catch(_reject);
  4465.  
  4466. function onHeadersReceived(gmResponse) {
  4467. const { readyState, responseHeaders, status, statusText, response: readableStream } = gmResponse;
  4468.  
  4469. if (readyState === HEADERS_RECEIVED) {
  4470. const headers = parseHeaders(responseHeaders);
  4471. let newResp;
  4472.  
  4473. if (status === 0) {
  4474. newResp = new Response(readableStream, { headers /*status, statusText*/ });
  4475. } else {
  4476. newResp = new Response(readableStream, { headers, status, statusText });
  4477. }
  4478.  
  4479. resolve(newResp);
  4480. }
  4481. }
  4482. });
  4483. }
  4484. }
  4485.  
  4486. /**
  4487. *
  4488. *
  4489. * @author Michael Barros <michaelcbarros@gmail.com>
  4490. * @param {T} items
  4491. * @param {{name: string, desc: boolean, case_sensitive: boolean}[]} columns
  4492. * @param {{cmpFunc: any}} [{ cmpFunc = cmp }={}]
  4493. * @returns {T}
  4494. * @template T
  4495. */
  4496. function multiKeySort(items, columns, { cmpFunc = null } = {}) {
  4497. function cmp(a, b) {
  4498. if (a < b) {
  4499. return -1;
  4500. } else {
  4501. if (a > b) {
  4502. return 1;
  4503. } else {
  4504. return 0;
  4505. }
  4506. }
  4507. }
  4508.  
  4509. cmpFunc = cmpFunc != null ? cmpFunc : cmp;
  4510.  
  4511. let comparers = [];
  4512.  
  4513. columns.forEach((col) => {
  4514. let column = col.name;
  4515. let desc = 'desc' in col ? col.desc : false;
  4516. let case_sensitive = 'case_sensitive' in col ? col.case_sensitive : true;
  4517.  
  4518. comparers.push([column, desc ? -1 : 1, case_sensitive]);
  4519. });
  4520.  
  4521. function comparer(left, right) {
  4522. for (let i = 0; i < comparers.length; i++) {
  4523. const column = comparers[i][0];
  4524. const polarity = comparers[i][1];
  4525. const case_sensitive = comparers[i][2];
  4526.  
  4527. let result = 0;
  4528.  
  4529. if (case_sensitive) {
  4530. result = cmpFunc(left[column], right[column]);
  4531. } else {
  4532. result = cmpFunc(left[column].toLowerCase(), right[column].toLowerCase());
  4533. }
  4534.  
  4535. if (result) {
  4536. return polarity * result;
  4537. }
  4538. }
  4539.  
  4540. return 0;
  4541. }
  4542.  
  4543. return items.sort(comparer);
  4544. }
  4545.  
  4546. /**
  4547. *
  4548. *
  4549. * @author Michael Barros <michaelcbarros@gmail.com>
  4550. * @param {string} text
  4551. * @param {string} [nodeType='div']
  4552. * @returns {HTMLElement}
  4553. */
  4554. function getElementByTextContent(text, nodeType = 'div') {
  4555. let xpath = `//${nodeType}[text()='${text}']`;
  4556.  
  4557. /** @type {HTMLElement} */
  4558. let elem = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  4559.  
  4560. return elem;
  4561. }
  4562.  
  4563. /**
  4564. *
  4565. *
  4566. * @author Michael Barros <michaelcbarros@gmail.com>
  4567. * @param {string} text
  4568. * @param {string} [nodeType='div']
  4569. * @returns {HTMLElement}
  4570. */
  4571. function getElementByTextContentContains(text, nodeType = 'div') {
  4572. let xpath = `//${nodeType}[contains(text(),'${text}')]`;
  4573.  
  4574. /** @type {HTMLElement} */
  4575. let elem = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
  4576.  
  4577. return elem;
  4578. }
  4579.  
  4580. /**
  4581. *
  4582. *
  4583. * @author Michael Barros <michaelcbarros@gmail.com>
  4584. * @param {HTMLElement} element
  4585. * @param {(elem: HTMLElement) => void} callback
  4586. */
  4587. function onRemove(element, callback) {
  4588. const parent = element.parentNode;
  4589.  
  4590. if (!parent) {
  4591. throw new Error('The node must already be attached');
  4592. }
  4593.  
  4594. const observer = new MutationObserver((mutations) => {
  4595. let removed = false;
  4596.  
  4597. for (const mutation of mutations) {
  4598. for (const node of mutation.removedNodes) {
  4599. if (node === element) {
  4600. observer.disconnect();
  4601.  
  4602. callback(element);
  4603.  
  4604. removed = true;
  4605.  
  4606. break;
  4607. }
  4608. }
  4609.  
  4610. if (removed) {
  4611. break;
  4612. }
  4613. }
  4614. });
  4615.  
  4616. observer.observe(parent, {
  4617. childList: true,
  4618. });
  4619. }
  4620.  
  4621. // #endregion Helper Functions
  4622.  
  4623. // #region Prototype Functions
  4624.  
  4625. // #region Array
  4626.  
  4627. /**
  4628. * Function to setup custom prototype functions for the Array class
  4629. * (For no conflicts, Object.defineProperty must be used)
  4630. *
  4631. */
  4632. function setupArrayPrototypes() {
  4633. let funcs = [
  4634. function pushUnique(item) {
  4635. let index = -1;
  4636.  
  4637. for (let i = 0; i < this.length; i++) {
  4638. if (equals(this[i], item)) index = i;
  4639. }
  4640.  
  4641. if (index === -1) this.push(item);
  4642. },
  4643. ];
  4644.  
  4645. for (let i = 0; i < funcs.length; i++) {
  4646. let func = funcs[i];
  4647.  
  4648. Object.defineProperty(Array.prototype, func.name, {
  4649. enumerable: false,
  4650. configurable: true,
  4651. writable: true,
  4652. value: func,
  4653. });
  4654. }
  4655. }
  4656.  
  4657. setupArrayPrototypes();
  4658.  
  4659. // #endregion Array
  4660.  
  4661. // #endregion Prototype Functions
  4662.  
  4663. // #region jQuery
  4664.  
  4665. function setupJqueryExtendedFuncs() {
  4666. if ('jQuery' in getWindow() || 'jQuery' in window) {
  4667. jQuery.fn.extend({
  4668. /**
  4669. *
  4670. *
  4671. * @author Michael Barros <michaelcbarros@gmail.com>
  4672. * @returns {boolean}
  4673. * @this {JQuery<HTMLElement>}
  4674. */
  4675. exists: function exists() {
  4676. return this.length !== 0;
  4677. },
  4678.  
  4679. /**
  4680. *
  4681. *
  4682. * @author Michael Barros <michaelcbarros@gmail.com>
  4683. * @param {() => void} callback
  4684. * @returns {JQuery<HTMLElement>}
  4685. * @this {JQuery<HTMLElement>}
  4686. */
  4687. ready: function ready(callback) {
  4688. let cb = function cb() {
  4689. return setTimeout(callback, 0, jQuery);
  4690. };
  4691.  
  4692. if (document.readyState !== 'loading') {
  4693. cb();
  4694. } else {
  4695. document.addEventListener('DOMContentLoaded', cb);
  4696. }
  4697.  
  4698. return this;
  4699. },
  4700.  
  4701. /**
  4702. *
  4703. *
  4704. * @author Michael Barros <michaelcbarros@gmail.com>
  4705. * @param {string} method
  4706. * @param {{}} options
  4707. * @returns {number | string | null}
  4708. * @this {JQuery<HTMLElement>}
  4709. */
  4710. actual: function actual(method, options) {
  4711. // check if the jQuery method exist
  4712. if (!this[method]) {
  4713. throw '$.actual => The jQuery method "' + method + '" you called does not exist';
  4714. }
  4715.  
  4716. let defaults = {
  4717. absolute: false,
  4718. clone: false,
  4719. includeMargin: false,
  4720. display: 'block',
  4721. };
  4722.  
  4723. let configs = jQuery.extend(defaults, options);
  4724.  
  4725. let $target = this.eq(0);
  4726. let fix;
  4727. let restore;
  4728.  
  4729. if (configs.clone === true) {
  4730. fix = function () {
  4731. let style = 'position: absolute !important; top: -1000 !important; ';
  4732.  
  4733. // this is useful with css3pie
  4734. $target = $target.clone().attr('style', style).appendTo('body');
  4735. };
  4736.  
  4737. restore = function () {
  4738. // remove DOM element after getting the width
  4739. $target.remove();
  4740. };
  4741. } else {
  4742. let tmp = [];
  4743. let style = '';
  4744. let $hidden;
  4745.  
  4746. fix = function () {
  4747. // get all hidden parents
  4748. $hidden = $target.parents().addBack().filter(':hidden');
  4749. style += 'visibility: hidden !important; display: ' + configs.display + ' !important; ';
  4750.  
  4751. if (configs.absolute === true) {
  4752. style += 'position: absolute !important; ';
  4753. }
  4754.  
  4755. // save the origin style props
  4756. // set the hidden el css to be got the actual value later
  4757. $hidden.each(function () {
  4758. // Save original style. If no style was set, attr() returns undefined
  4759. let $this = jQuery(this);
  4760. let thisStyle = $this.attr('style');
  4761.  
  4762. tmp.push(thisStyle);
  4763.  
  4764. // Retain as much of the original style as possible, if there is one
  4765. $this.attr('style', thisStyle ? thisStyle + ';' + style : style);
  4766. });
  4767. };
  4768.  
  4769. restore = function () {
  4770. // restore origin style values
  4771. $hidden.each(function (i) {
  4772. let $this = jQuery(this);
  4773. let _tmp = tmp[i];
  4774.  
  4775. if (_tmp === undefined) {
  4776. $this.removeAttr('style');
  4777. } else {
  4778. $this.attr('style', _tmp);
  4779. }
  4780. });
  4781. };
  4782. }
  4783.  
  4784. fix();
  4785. // get the actual value with user specific methed
  4786. // it can be 'width', 'height', 'outerWidth', 'innerWidth'... etc
  4787. // configs.includeMargin only works for 'outerWidth' and 'outerHeight'
  4788. let actual = /(outer)/.test(method) ? $target[method](configs.includeMargin) : $target[method]();
  4789.  
  4790. restore();
  4791. // IMPORTANT, this plugin only return the value of the first element
  4792. return actual;
  4793. },
  4794.  
  4795. /**
  4796. *
  4797. *
  4798. * @author Michael Barros <michaelcbarros@gmail.com>
  4799. * @returns {DOMRect}
  4800. * @this {JQuery<HTMLElement>}
  4801. */
  4802. rect: function rect() {
  4803. return this[0].getBoundingClientRect();
  4804. },
  4805.  
  4806. /**
  4807. *
  4808. *
  4809. * @author Michael Barros <michaelcbarros@gmail.com>
  4810. * @param {number} levels
  4811. * @returns {JQuery<HTMLElement>}
  4812. * @this {JQuery<HTMLElement>}
  4813. */
  4814. parentEx: function parentEx(levels = 1) {
  4815. let parent = this;
  4816.  
  4817. for (let i = 0; i < levels; i++) {
  4818. if (parent.parent().length == 0) {
  4819. break;
  4820. }
  4821.  
  4822. parent = parent.parent();
  4823. }
  4824.  
  4825. return parent;
  4826. },
  4827. });
  4828. }
  4829. }
  4830.  
  4831. setupJqueryExtendedFuncs();
  4832.  
  4833. // #endregion jQuery