Home Reference Source

cables_dev/cables/src/core/utils.js

  1.  
  2. /**
  3. * @namespace external:CABLES#Utils
  4. */
  5.  
  6. import { CONSTANTS } from "./constants.js";
  7.  
  8. const UTILS = {};
  9. /**
  10. * Merge two Float32Arrays.
  11. * @function float32Concat
  12. * @memberof Utils
  13. * @param {Float32Array} first Left-hand side array
  14. * @param {Float32Array} second Right-hand side array
  15. * @return {Float32Array}
  16. * @static
  17. */
  18. UTILS.float32Concat = function (first, second)
  19. {
  20. if (!(first instanceof Float32Array)) first = new Float32Array(first);
  21. if (!(second instanceof Float32Array)) second = new Float32Array(second);
  22.  
  23. const result = new Float32Array(first.length + second.length);
  24.  
  25. result.set(first);
  26. result.set(second, first.length);
  27.  
  28. return result;
  29. };
  30.  
  31. /**
  32. * get op shortname: only last part of fullname and without version
  33. * @function getShortOpName
  34. * @memberof CABLES
  35. * @param {string} fullname full op name
  36. * @static
  37. */
  38. export const getShortOpName = function (fullname)
  39. {
  40. let name = fullname.split(".")[fullname.split(".").length - 1];
  41.  
  42. if (name.contains(CONSTANTS.OP.OP_VERSION_PREFIX))
  43. {
  44. const n = name.split(CONSTANTS.OP.OP_VERSION_PREFIX)[1];
  45. name = name.substring(0, name.length - (CONSTANTS.OP.OP_VERSION_PREFIX + n).length);
  46. }
  47. return name;
  48. };
  49.  
  50. /**
  51. * randomize order of an array
  52. * @function shuffleArray
  53. * @memberof Utils
  54. * @param {Array|Float32Array} array {Array} original
  55. * @return {Array|Float32Array} shuffled array
  56. * @static
  57. */
  58. export const shuffleArray = function (array)
  59. {
  60. for (let i = array.length - 1; i > 0; i--)
  61. {
  62. const j = Math.floor(Math.seededRandom() * (i + 1));
  63. const temp = array[i];
  64. array[i] = array[j];
  65. array[j] = temp;
  66. }
  67. return array;
  68. };
  69.  
  70.  
  71. /**
  72. * generate a short "relativly unique" id
  73. * @function shortId
  74. * @memberof Utils
  75. * @return {String} generated ID
  76. * @static
  77. */
  78.  
  79. const _shortIds = {};
  80. const _shortId = function ()
  81. {
  82. let str = Math.random().toString(36).substr(2, 9);
  83.  
  84. if (_shortIds.hasOwnProperty(str)) str = _shortId();
  85. _shortIds[str] = true;
  86. return str;
  87. };
  88. export const shortId = _shortId;
  89.  
  90.  
  91. /**
  92. * generate a UUID
  93. * @function uuid
  94. * @memberof Utils
  95. * @return {String} generated UUID
  96. * @static
  97. */
  98. const _uuid = function ()
  99. {
  100. let d = new Date().getTime();
  101. const uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) =>
  102. {
  103. const r = (d + Math.random() * 16) % 16 | 0;
  104. d = Math.floor(d / 16);
  105. return (c == "x" ? r : (r & 0x3) | 0x8).toString(16);
  106. });
  107. return uuid;
  108. };
  109. export const uuid = _uuid;
  110. export const generateUUID = _uuid;
  111.  
  112.  
  113.  
  114. export function cleanJson(obj)
  115. {
  116. for (const i in obj)
  117. {
  118. if (obj[i] && typeof objValue === "object" && obj[i].constructor === Object) obj[i] = cleanJson(obj[i]);
  119.  
  120. if (obj[i] === null || obj[i] === undefined) delete obj[i];
  121. else if (Array.isArray(obj[i]) && obj[i].length == 0) delete obj[i];
  122. }
  123.  
  124. return obj;
  125. }
  126.  
  127.  
  128. /**
  129. * @see http://stackoverflow.com/q/7616461/940217
  130. * @memberof Utils
  131. * @param str
  132. * @param prefix
  133. * @return {string}
  134. */
  135. const _prefixedHash = function (str, prefix = "id")
  136. {
  137. let hash = 0;
  138. if (Array.prototype.reduce)
  139. {
  140. hash = str.split("").reduce((a, b) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0);
  141. }
  142. else
  143. {
  144. if (str.length > 0)
  145. {
  146. for (let i = 0; i < str.length; i++)
  147. {
  148. let character = str.charCodeAt(i);
  149. hash = ((hash << 5) - hash) + character;
  150. hash &= hash; // Convert to 32bit integer
  151. }
  152. }
  153. }
  154. return prefix + "" + hash;
  155. };
  156. export const prefixedHash = _prefixedHash;
  157.  
  158. /**
  159. * generate a simple ID
  160. * @function simpleId
  161. * @memberof Utils
  162. * @return {Number} new id
  163. * @static
  164. */
  165. let simpleIdCounter = 0;
  166. export const simpleId = function ()
  167. {
  168. simpleIdCounter++;
  169. return simpleIdCounter;
  170. };
  171.  
  172. /**
  173. * smoothStep a value
  174. * @function smoothStep
  175. * @memberof Utils
  176. * @function
  177. * @param {Number} perc value value to be smoothed [0-1]
  178. * @return {Number} smoothed value
  179. * @static
  180. */
  181. export const smoothStep = function (perc)
  182. {
  183. const x = Math.max(0, Math.min(1, (perc - 0) / (1 - 0)));
  184. perc = x * x * (3 - 2 * x); // smoothstep
  185. return perc;
  186. };
  187.  
  188. /**
  189. * smootherstep a value
  190. * @function smootherStep
  191. * @memberof Utils
  192. * @param {Number} perc value to be smoothed [0-1]
  193. * @return {Number} smoothed value
  194. * @static
  195. */
  196. export const smootherStep = function (perc)
  197. {
  198. const x = Math.max(0, Math.min(1, (perc - 0) / (1 - 0)));
  199. perc = x * x * x * (x * (x * 6 - 15) + 10); // smootherstep
  200. return perc;
  201. };
  202.  
  203.  
  204. /**
  205. * clamp number / make sure its between min/max
  206. * @function clamp
  207. * @memberof Utils
  208. * @param {Number} value value to be mapped
  209. * @param {Number} min minimum value
  210. * @param {Number} max maximum value
  211. * @static
  212. */
  213. export const clamp = function (value, min, max)
  214. {
  215. return Math.min(Math.max(value, min), max);
  216. };
  217.  
  218. /**
  219. * map a value in a range to a value in another range
  220. * @function map
  221. * @memberof Utils
  222. * @param {Number} x value to be mapped
  223. * @param {Number} _oldMin old range minimum value
  224. * @param {Number} _oldMax old range maximum value
  225. * @param {Number} _newMin new range minimum value
  226. * @param {Number} _newMax new range maximum value
  227. * @param {Number} _easing
  228. * @return {Number} mapped value
  229. * @static
  230. */
  231. export const map = function (x, _oldMin, _oldMax, _newMin, _newMax, _easing)
  232. {
  233. if (x >= _oldMax) return _newMax;
  234. if (x <= _oldMin) return _newMin;
  235.  
  236. let reverseInput = false;
  237. const oldMin = Math.min(_oldMin, _oldMax);
  238. const oldMax = Math.max(_oldMin, _oldMax);
  239. if (oldMin != _oldMin) reverseInput = true;
  240.  
  241. let reverseOutput = false;
  242. const newMin = Math.min(_newMin, _newMax);
  243. const newMax = Math.max(_newMin, _newMax);
  244. if (newMin != _newMin) reverseOutput = true;
  245.  
  246. let portion = 0;
  247. let r = 0;
  248.  
  249. if (reverseInput) portion = ((oldMax - x) * (newMax - newMin)) / (oldMax - oldMin);
  250. else portion = ((x - oldMin) * (newMax - newMin)) / (oldMax - oldMin);
  251.  
  252. if (reverseOutput) r = newMax - portion;
  253. else r = portion + newMin;
  254.  
  255. if (!_easing) return r;
  256. if (_easing == 1)
  257. {
  258. // smoothstep
  259. x = Math.max(0, Math.min(1, (r - _newMin) / (_newMax - _newMin)));
  260. return _newMin + x * x * (3 - 2 * x) * (_newMax - _newMin);
  261. }
  262. if (_easing == 2)
  263. {
  264. // smootherstep
  265. x = Math.max(0, Math.min(1, (r - _newMin) / (_newMax - _newMin)));
  266. return _newMin + x * x * x * (x * (x * 6 - 15) + 10) * (_newMax - _newMin);
  267. }
  268.  
  269. return r;
  270. };
  271.  
  272. /**
  273. * @namespace Math
  274. */
  275. /**
  276. * set random seed for seededRandom()
  277. * @memberof Math
  278. * @type Number
  279. * @static
  280. */
  281. Math.randomSeed = 1;
  282.  
  283.  
  284. Math.setRandomSeed = function (seed)
  285. {
  286. // https://github.com/cables-gl/cables_docs/issues/622
  287. Math.randomSeed = seed * 50728129;
  288. if (seed != 0)
  289. {
  290. Math.randomSeed = Math.seededRandom() * 17624813;
  291. Math.randomSeed = Math.seededRandom() * 9737333;
  292. }
  293. };
  294.  
  295.  
  296. /**
  297. * generate a seeded random number
  298. * @function seededRandom
  299. * @memberof Math
  300. * @param {Number} max minimum possible random number
  301. * @param {Number} min maximum possible random number
  302. * @return {Number} random value
  303. * @static
  304. */
  305. Math.seededRandom = function (max, min)
  306. {
  307. if (Math.randomSeed === 0) Math.randomSeed = Math.random() * 999;
  308. max = max || 1;
  309. min = min || 0;
  310.  
  311. Math.randomSeed = (Math.randomSeed * 9301 + 49297) % 233280;
  312. const rnd = Math.randomSeed / 233280.0;
  313.  
  314. return min + rnd * (max - min);
  315. };
  316.  
  317.  
  318. // ----------------------------------------------------------------
  319.  
  320. /**
  321. * returns true if parameter is a number
  322. * @function isNumeric
  323. * @memberof Utils
  324. * @param {Any} n value The value to check.
  325. * @return {Boolean}
  326. * @static
  327. */
  328. UTILS.isNumeric = function (n)
  329. {
  330. return !isNaN(parseFloat(n)) && isFinite(n);
  331. };
  332.  
  333. /**
  334. * returns true if parameter is array
  335. * @function isArray
  336. * @param {Any} v value Value to check
  337. * @memberof Utils
  338. * @return {Boolean}
  339. * @static
  340. */
  341. UTILS.isArray = function (v)
  342. {
  343. return Object.prototype.toString.call(v) === "[object Array]";
  344. };
  345.  
  346. /**
  347. * @namespace String
  348. */
  349.  
  350. /**
  351. * append a linebreak to a string
  352. * @function endl
  353. * @memberof String
  354. * @return {String} string with newline break appended ('\n')
  355. */
  356. String.prototype.endl = function ()
  357. {
  358. return this + "\n";
  359. };
  360.  
  361. /**
  362. * return true if string starts with prefix
  363. * @function startsWith
  364. * @memberof String
  365. * @param {String} prefix The prefix to check.
  366. * @return {Boolean}
  367. */
  368. String.prototype.startsWith = function (prefix)
  369. {
  370. if (!this || !prefix) return false;
  371. if (this.length >= prefix.length)
  372. {
  373. if (this.substring(0, prefix.length) == prefix) return true;
  374. }
  375. return false;
  376. // return this.indexOf(prefix) === 0;
  377. };
  378.  
  379. /**
  380. * return true if string ends with suffix
  381. * @function endsWith
  382. * @memberof String
  383. * @param {String} suffix
  384. * @return {Boolean}
  385. */
  386. String.prototype.endsWith = String.prototype.endsWith || function (suffix)
  387. {
  388. return this.match(suffix + "$") == suffix;
  389. };
  390.  
  391. /**
  392. * return true if string contains string
  393. * @function contains
  394. * @memberof String
  395. * @param {String} searchStr
  396. * @return {Boolean}
  397. */
  398. String.prototype.contains = String.prototype.contains || function (searchStr)
  399. {
  400. return this.indexOf(searchStr) > -1;
  401. };
  402.  
  403.  
  404.  
  405. // ----------------------------------------------------------------
  406.  
  407. /**
  408. * append a unique/random parameter to a url, so the browser is forced to reload the file, even if its cached
  409. * @function cacheBust
  410. * @static
  411. * @memberof Utils
  412. * @param {String} url The url to append the cachebuster parameter to.
  413. * @return {String} url with cachebuster parameter
  414. */
  415. export const cacheBust = function (url = "")
  416. {
  417. if (!url) return "";
  418. if (url.startsWith("data:")) return;
  419. if (url.contains("?")) url += "&";
  420. else url += "?";
  421. return url + "cache=" + CABLES.uuid();
  422. };
  423.  
  424. /**
  425. * copy the content of an array
  426. * @function copyArray
  427. * @static
  428. * @memberof Utils
  429. * @param {Array} src sourceArray
  430. * @param {Array} dst optional
  431. * @return {Array} dst
  432. */
  433. export const copyArray = function (src, dst)
  434. {
  435. if (!src) return null;
  436. dst = dst || [];
  437. dst.length = src.length;
  438. for (let i = 0; i < src.length; i++)
  439. {
  440. dst[i] = src[i];
  441. }
  442.  
  443. return dst;
  444. };
  445.  
  446.  
  447. /**
  448. * return the filename part of a url without extension
  449. * @function basename
  450. * @static
  451. * @memberof Utils
  452. * @param {String} url
  453. * @return {String} just the filename
  454. */
  455. export const basename = function (url)
  456. {
  457. let name = CABLES.filename(url);
  458.  
  459. const parts2 = name.split(".");
  460. name = parts2[0];
  461.  
  462. return name;
  463. };
  464.  
  465. /**
  466. * output a stacktrace to the console
  467. * @function logStack
  468. * @static
  469. * @memberof Utils
  470. */
  471. export const logStack = function ()
  472. {
  473. console.log("logstack", (new Error()).stack);
  474. };
  475.  
  476. /**
  477. * return the filename part of a url
  478. * @function filename
  479. * @static
  480. * @memberof Utils
  481. * @param {String} url
  482. * @return {String} just the filename
  483. */
  484. export const filename = function (url)
  485. {
  486. let name = "";
  487. if (!url) return "";
  488.  
  489. if (url.startsWith("data:") && url.contains(":"))
  490. {
  491. const parts = url.split(",");
  492. return parts[0];
  493. }
  494.  
  495. let parts = (url + "").split("/");
  496. if (parts.length > 0)
  497. {
  498. const str = parts[parts.length - 1];
  499. let parts2 = str.split("?");
  500. name = parts2[0];
  501. }
  502.  
  503. return name || "";
  504. };
  505.  
  506.  
  507. export const ajaxSync = function (url, cb, method, post, contenttype)
  508. {
  509. request({
  510. "url": url,
  511. "cb": cb,
  512. "method": method,
  513. "data": post,
  514. "contenttype": contenttype,
  515. "sync": true,
  516. });
  517. };
  518.  
  519. /**
  520. * make an ajax request
  521. * @static
  522. * @function ajax
  523. * @param url
  524. * @param cb
  525. * @param method
  526. * @param post
  527. * @param contenttype
  528. * @param jsonP
  529. * @param headers
  530. * @param options
  531. */
  532. export const ajax = function (url, cb, method, post, contenttype, jsonP, headers = {}, options = {})
  533. {
  534. const requestOptions = {
  535. "url": url,
  536. "cb": cb,
  537. "method": method,
  538. "data": post,
  539. "contenttype": contenttype,
  540. "sync": false,
  541. "jsonP": jsonP,
  542. "headers": headers,
  543. };
  544. if (options && options.credentials) requestOptions.credentials = options.credentials;
  545. request(requestOptions);
  546. };
  547.  
  548. export const request = function (options)
  549. {
  550. if (!options.hasOwnProperty("asynch")) options.asynch = true;
  551.  
  552. let xhr;
  553. try
  554. {
  555. xhr = new XMLHttpRequest();
  556. }
  557. catch (e) {}
  558.  
  559. xhr.onreadystatechange = function ()
  560. {
  561. if (xhr.readyState != 4) return;
  562.  
  563. if (options.cb)
  564. {
  565. if (xhr.status == 200 || xhr.status == 0) options.cb(false, xhr.responseText, xhr);
  566. else options.cb(true, xhr.responseText, xhr);
  567. }
  568. };
  569.  
  570. try
  571. {
  572. xhr.open(options.method ? options.method.toUpperCase() : "GET", options.url, !options.sync);
  573. }
  574. catch (e)
  575. {
  576. if (options.cb && e) options.cb(true, e.msg, xhr);
  577. }
  578.  
  579. if (typeof options.headers === "object")
  580. {
  581. if (options.headers)
  582. {
  583. const keys = Object.keys(options.headers);
  584. for (let i = 0; i < keys.length; i++)
  585. {
  586. const name = keys[i];
  587. const value = options.headers[name];
  588. xhr.setRequestHeader(name, value);
  589. }
  590. }
  591. }
  592.  
  593. if (options.credentials && options.credentials !== "omit")
  594. {
  595. xhr.withCredentials = true;
  596. }
  597.  
  598. try
  599. {
  600. if (!options.post && !options.data)
  601. {
  602. xhr.send();
  603. }
  604. else
  605. {
  606. xhr.setRequestHeader(
  607. "Content-type",
  608. options.contenttype ? options.contenttype : "application/x-www-form-urlencoded",
  609. );
  610. xhr.send(options.data || options.post);
  611. }
  612. }
  613. catch (e)
  614. {
  615. if (options.cb) options.cb(true, e.msg, xhr);
  616. }
  617. };
  618.  
  619.  
  620. export const keyCodeToName = function (keyCode)
  621. {
  622. if (!keyCode && keyCode !== 0) return "Unidentified";
  623. const keys = {
  624. "8": "Backspace",
  625. "9": "Tab",
  626. "12": "Clear",
  627. "13": "Enter",
  628. "16": "Shift",
  629. "17": "Control",
  630. "18": "Alt",
  631. "19": "Pause",
  632. "20": "CapsLock",
  633. "27": "Escape",
  634. "32": "Space",
  635. "33": "PageUp",
  636. "34": "PageDown",
  637. "35": "End",
  638. "36": "Home",
  639. "37": "ArrowLeft",
  640. "38": "ArrowUp",
  641. "39": "ArrowRight",
  642. "40": "ArrowDown",
  643. "45": "Insert",
  644. "46": "Delete",
  645. "112": "F1",
  646. "113": "F2",
  647. "114": "F3",
  648. "115": "F4",
  649. "116": "F5",
  650. "117": "F6",
  651. "118": "F7",
  652. "119": "F8",
  653. "120": "F9",
  654. "121": "F10",
  655. "122": "F11",
  656. "123": "F12",
  657. "144": "NumLock",
  658. "145": "ScrollLock",
  659. "224": "Meta"
  660. };
  661. if (keys[keyCode])
  662. {
  663. return keys[keyCode];
  664. }
  665. else
  666. {
  667. return String.fromCharCode(keyCode);
  668. }
  669. };
  670. // ----------------------------------------------------------------
  671.  
  672. window.performance = window.performance || {
  673. "offset": Date.now(),
  674. "now": function now()
  675. {
  676. return Date.now() - this.offset;
  677. },
  678. };
  679.  
  680.  
  681. export const logErrorConsole = function (initiator)
  682. {
  683. CABLES.errorConsole = CABLES.errorConsole || { "log": [] };
  684. CABLES.errorConsole.log.push({ "initiator": initiator, "arguments": arguments });
  685.  
  686. if (!CABLES.errorConsole.ele)
  687. {
  688. const ele = document.createElement("div");
  689. ele.id = "cablesErrorConsole";
  690. ele.style.width = "90%";
  691. ele.style.height = "300px";
  692. ele.style.zIndex = "9999999";
  693. ele.style.display = "inline-block";
  694. ele.style.position = "absolute";
  695. ele.style.padding = "10px";
  696. ele.style.fontFamily = "monospace";
  697. ele.style.color = "red";
  698. ele.style.backgroundColor = "#200";
  699.  
  700. CABLES.errorConsole.ele = ele;
  701. document.body.appendChild(ele);
  702. }
  703.  
  704. let logHtml = "ERROR<br/>for more info, open your browsers dev tools console (Ctrl+Shift+I or Command+Alt+I)<br/>";
  705.  
  706. for (let l = 0; l < CABLES.errorConsole.log.length; l++)
  707. {
  708. logHtml += CABLES.errorConsole.log[l].initiator + " ";
  709. for (let i = 1; i < CABLES.errorConsole.log[l].arguments.length; i++)
  710. {
  711. if (i > 2)logHtml += ", ";
  712. let arg = CABLES.errorConsole.log[l].arguments[i];
  713. if (arg.constructor.name.indexOf("Error") > -1 || arg.constructor.name.indexOf("error") > -1)
  714. {
  715. let txt = "Uncaught ErrorEvent ";
  716. if (arg.message)txt += " message: " + arg.message;
  717. logHtml += txt;
  718. }
  719. else if (typeof arg == "string")
  720. logHtml += arg;
  721. else if (typeof arg == "number")
  722. logHtml += String(arg) + " ";
  723. }
  724. logHtml += "<br/>";
  725. }
  726.  
  727.  
  728. CABLES.errorConsole.ele.innerHTML = logHtml;
  729. };
  730.  
  731.  
  732. export { UTILS };