1const STOP_EVENT_TIME = 250, 2 ROUND_DEC = 2, 3 CHILD_WINDOW_ID = "child-window", 4 CHILD_HAS_MODAL_CLASS = "has-modal"; 5 6var STOP_EVENT = {}; 7 8/* Type conversion */ 9function numberize(s) { 10 if (s === "blank") { 11 return 0; 12 } 13 if (s === "false") { 14 return 0; 15 } 16 if (s === "true") { 17 return 1; 18 } 19 if (s === "infinity") { 20 return Infinity; 21 } 22 if (s === "neginfinity") { 23 return -Infinity; 24 } 25 if (typeof s == "string") { 26 if (!/[0-9]+/.test(s)) { 27 return NaN; 28 } else { 29 s = s.match(/(-?\.?[0-9]+\.?[0-9]*)/)[1]; 30 } 31 } 32 return parseFloat(s); 33} 34 35/* HTML */ 36const copy_to_clipboard = (str) => { 37 const el = document.createElement("textarea"); 38 el.value = str; 39 document.body.appendChild(el); 40 el.select(); 41 document.execCommand("copy"); 42 document.body.removeChild(el); 43}; 44 45const linkify = (text, href, options = {}) => ` 46 <a class="link no-decoration ${ 47 options.class ? options.class : "" 48 }" href="${href}" target="_blank" ${ 49 options.extra_attributes ? options.extra_attributes : "" 50}}>${text}</a>`; 51 52/* DOM utilities */ 53 54const show_flex = (elem) => elem.css("display", "flex"); 55 56function isDomElem(el) { 57 return el instanceof HTMLElement || el[0] instanceof HTMLElement 58 ? true 59 : false; 60} 61 62/* Object utilities */ 63 64// Validation 65const validObject = (obj) => 66 obj && typeof obj == "object" && !jQuery.isEmptyObject(obj); 67 68// Extend Object.prototype 69Object.filter = (obj, predicate, bykey = false) => 70 bykey 71 ? Object.keys(obj) 72 .filter((key) => predicate(key)) 73 .reduce((res, key) => ((res[key] = obj[key]), res), {}) 74 : Object.keys(obj) 75 .filter((key) => predicate(obj[key])) 76 .reduce((res, key) => ((res[key] = obj[key]), res), {}); 77 78Object.get_key = (obj, val) => Object.keys(obj).find((key) => obj[key] === val); 79 80Object.rekey = (obj, keysMap) => 81 Object.entries(obj).reduce((rk, [k, v]) => { 82 if (k in keysMap) rk[keysMap[k]] = v; 83 else rk[k] = v; 84 return rk; 85 }, {}); 86 87Object.remove_keys = (obj, keys) => { 88 for (let k of keys) delete obj[k]; 89}; 90 91Object.map = (obj, mapping, bykey = false) => 92 bykey 93 ? Object.keys(obj).reduce((m, k) => ((m[mapping(k)] = obj[k]), m), {}) 94 : Object.keys(obj).reduce((m, k) => ((m[k] = mapping(obj[k])), m), {}); 95 96Object.identical = (obj1,obj2) => 97 Object.subset_of(obj1,obj2) && 98 Object.subset_of(obj2,obj1); 99 100Object.subset_of = (obj1,obj2) => 101 Object.entries(obj1).every( ([k,v]) => obj2[k] == v); 102 103Object.pop = function (obj, key) { 104 const value = obj[key]; 105 if (key in obj) delete obj[key]; 106 return value; 107}; 108 109/* String / number utilities */ 110 111// Rendering 112function stripquotes(x) { 113 try { 114 if (x[0] === '"' && x[x.length - 1] === '"') return x.slice(1, -1); 115 return x; 116 } catch(e) { 117 console.log(e); 118 } 119 120 return ""; 121} 122 123function strip_all_quotes(x) { 124 try { 125 return x.replace(/(^['"])|(['"]$)/g, ""); 126 } catch (e) { 127 console.log(e); 128 } 129 130 return ""; 131} 132 133const beautify_dollars = (x,options = {}) => { 134 135 const decoration = options.no_decoration ? false : true, 136 round = options.round; 137 138 x = typeof x == "string" ? stripquotes(x).replace(/,/g, "") : x; 139 x = numberize(x); 140 141 if (round) x = Math.round(x); 142 143 x = !isNaN(x) 144 ? x.toLocaleString('en-US',{ 145 style: "currency", 146 currency: "USD" 147 }) 148 : ""; 149 150 if (x && !decoration) x = x.split("$").join(""); 151 152 if (round) return x.slice(0,-3); 153 154 return x; 155} 156 157function convert_dollars_to_cents(dollars) { 158 159 if (typeof dollars == "string") 160 dollars = numberize(dollars); 161 162 return Math.round(dollars * 100); 163} 164 165 166const beautify_number = (x, include_decimal = false) => { 167 x = typeof x == "string" ? stripquotes(x).replace(/,/g, "") : x; 168 x = numberize(x); 169 x = include_decimal ? x.toFixed(include_decimal) : Math.round(x); 170 return !isNaN(x) 171 ? x.toLocaleString("en-US", { 172 useGrouping: true, 173 }) 174 : ""; 175}; 176 177const debeautify_number = (x) => { 178 if (typeof x === "number") return x; 179 if (typeof x !== "string") return NaN; 180 181 var neg = 1; 182 if (x[0] === "-") neg = -1; 183 184 x = x.replace(/[^0-9\.]/g, "").trim(); 185
186 return x.length ? Number(x) * neg : false; 187}; 188 189const number_length_x = (n, x) => { 190 if (typeof n === "number") n = String(n); 191 if (n.length < x) return number_length_x(`0${n}`, x); 192 return n; 193}; 194 195function format_phone_number(value) { 196 // if input value is falsy eg if the user deletes the input, then just return 197 if (!value) return value; 198 199 // clean the input for any non-digit values. 200 let phoneNumber = value.replace(/[^\d]/g, ''); 201 202 // phoneNumberLength is used to know when to apply our formatting for the phone number 203 let phoneNumberLength = phoneNumber.length; 204 205 if (phoneNumberLength > 10 && phoneNumber[0] == "1") { 206 phoneNumber = phoneNumber.slice(1); 207 phoneNumberLength -= 1 208 } 209 210 // we need to return the value with no formatting if its less than four digits 211 // this is to avoid weird behavior that occurs if you format the area code too early 212 if (phoneNumberLength < 4) return phoneNumber; 213 214 // if phoneNumberLength is greater than 4 and less the 7 we start to return 215 // the formatted number 216 if (phoneNumberLength < 7) { 217 return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice(3)}`; 218 } 219 220 // finally, if the phoneNumberLength is greater then seven, we add the last 221 // bit of formatting and return it. 222 return `(${phoneNumber.slice(0, 3)}) ${phoneNumber.slice( 223 3, 224 6 225 )}-${phoneNumber.slice(6, 10)}`; 226} 227 228function unstyle_phone_number(value) { 229 230 if (!value) return false; 231 232 let phoneNumber = value.replace(/[^\d]/g, ''), 233 phoneNumberLength = phoneNumber.length; 234 235 if (phoneNumberLength > 10 && phoneNumber[0] == "1") { 236 phoneNumber = phoneNumber.slice(1); 237 phoneNumberLength -= 1 238 } 239 240 if (phoneNumberLength > 10) phoneNumber = phoneNumber.slice(0,10); 241 242 if (phoneNumber.length != 10) return false; 243 244 return phoneNumber; 245} 246 247function validate_phone_number(value) { 248 return /^[0-9]{10}$/.test(value); 249} 250 251function safe_format_date_ext(date) { 252 let date_str = ""; 253 254 try { 255 const main_date = date.match(/^([0-9\/]+)/)[1], 256 [day, month, year] = main_date.split("/"); 257 date_str = `${day.padStart(2, "0")}/${month.padStart(2, "0")}/${year}`; 258 } catch (e) {} 259 260 return date_str; 261}; 262 263function format_date_ext(date) { 264 const [day, month, year] = date.split("/"); 265 return `${day.padStart(2, "0")}/${month.padStart(2, "0")}/${year}`; 266}; 267 268function format_date_string(value) { 269 if (!value) return value; 270 271 const ending_slash = value[value.length - 1] == "\/"; 272 let date_string = value.replace(/[^\d]/g, ''); 273 const date_string_length = date_string.length; 274 275 if (date_string_length <= 2) 276 return (ending_slash && date_string_length == 2) 277 ? `${date_string}/` 278 : date_string; 279 280 if (date_string_length <= 4) { 281 date_string = `${date_string.slice(0, 2)}/${date_string.slice(2,4)}`; 282 return (ending_slash && date_string_length == 4) 283 ? `${date_string}/` 284 : date_string; 285 } 286 287 return `${date_string.slice(0, 2)}/${date_string.slice(2, 4)}/${date_string.slice(4,8)}`; 288} 289 290function validate_date_string(value) { 291 try { 292 // Ensure expected format 293 if (/\d{2}\/\d{2}\/\d{4}/.test(value)) { 294 const date = moment(value); 295 return date.isValid(); 296 } 297 } catch(e) {} 298 299 return false; 300} 301 302function validate_ratio_string(value) { 303 return /^[0-9]+\.?[0-9]* : [0-9]+\.?[0-9]*$/.test(value); 304} 305 306function format_ratio_string(value) { 307 308 if (!value) return ""; 309 310 const match = value.match(/^\s*([0-9\.]+)[\s:]{1,}\s*([0-9\.]+)?\s*$/); 311 if (match?.length) { 312 const first_num = match[1]; 313 const second_num = match[2] || ""; 314 return `${first_num} : ${second_num}`; 315 } 316 317 return value; 318} 319 320const validate_state_code = (str) => STATE_CODES.includes(str); 321 322 323const NUM_UNITS = { 324 4: "million", 325 5: "ten-million", 326 6: "hundred-million", 327 7: "billion", 328 8: "ten-billion", 329 9: "hundred-billion", 330 10: "trillion", 331 11: "ten-trillion", 332 12: "hundred-trillion", 333}; 334 335const style_large_number = (str) => { 336 if (typeof str == "string") str = str.replace(/[^0-9]/, ""); 337 else str = String(str); 338 339 if (str.length < 7) return beautify_number(str); 340 341 var keep = str.slice(0, 3), 342 unit = str.slice(3), 343 rounder = parseInt(unit[0]), 344 num_removed = unit.length; 345 346 keep = parseInt(keep); 347
348 if (!keep || !rounder || !NUM_UNITS[unit.length]) return beautify_number(str); 349 350 if (rounder >= 5) keep += 1; 351 352 return beautify_number(keep) + " " + NUM_UNITS[unit.length]; 353}; 354 355const ensure_number = (x) => (typeof x == "number" ? x : parseInt(x) || 0); 356 357const parse_valid_int = (x) => { 358 if (typeof x === "number") return x; 359 360 try { 361 return parseInt(x); 362 } catch (e) { 363 return false; 364 } 365}; 366 367const falsy_not_zero = (value) => !value && value !== 0; 368 369const round_distance = (ft) => { 370 var ft_in_mi = 5280, 371 mi_cutoff = ft_in_mi / 4; 372 373 if (ft >= mi_cutoff) 374 return { suffix: " mi", value: ft / ft_in_mi, include_decimal: 2 }; 375 else return { suffix: " ft", value: ft }; 376}; 377 378const round_area = (sqft) => { 379 var sqft_in_ac = 43560, 380 sqft_in_sqmi = 27878400, 381 ac_cutoff = sqft_in_ac / 4, 382 sqmi_cuttoff = sqft_in_sqmi; 383 384 if (sqft >= sqmi_cuttoff) 385 return { suffix: " sqmi", value: sqft / sqft_in_sqmi, include_decimal: 2 }; 386 else if (sqft >= ac_cutoff) 387 return { suffix: " ac", value: sqft / sqft_in_ac, include_decimal
387: 2 }; 388 else return { suffix: " sqft", value: sqft }; 389}; 390 391const round_money = (dollar) => { 392 var dollar_in_k = 1000, 393 dollar_in_m = 1000000, 394 k_cutoff = dollar_in_k, 395 m_cuttoff = dollar_in_m; 396 397 if (dollar >= m_cuttoff) 398 return { 399 prefix: "$", 400 suffix: "M", 401 value: dollar / dollar_in_m, 402 include_decimal: 1, 403 }; 404 else if (dollar >= k_cutoff) 405 return { prefix: "$", suffix: "K", value: dollar / dollar_in_k }; 406 else return { prefix: "$", suffix: "", value: dollar }; 407}; 408 409const round_to_decimal = (num, dec) => { 410 if (typeof dec !== "number") dec = ROUND_DEC; 411 return Math.round((num + Number.EPSILON) * 10 ** dec) / 10 ** dec; 412}; 413 414const is_positive_number = (value, zeros_allowed = false) => { 415 value = Number(value); 416 if (isNaN(value)) return false; 417 if (value < 0 || (!zeros_allowed && value === 0)) return false; 418 return true; 419}; 420 421// Unit conversions 422 423const sqm_to_sqft = (sqm) => sqm * 10.7639; 424const ft_to_m = (ft) => ft * 0.3048; 425 426const get_current_styled_date = () => dateify_timestamp(Date.now()); 427 428const dateify_utc_timestamp = (ms, time = false) => { 429 var date = new Date(ms), 430 time_text = "", 431 is_am = date.getUTCHours() < 12, 432 hour = 433 date.getUTCHours() > 12 434 ? date.getUTCHours() - 12 435 : date.getUTCHours() === 0 436 ? 12 437 : date.getUTCHours(); 438 439 if (time) 440 time_text = ` at ${hour}:${number_length_x(date.getUTCMinutes(), 2)} ${ 441 is_am ? "AM" : "PM" 442 }`; 443 444 return `${number_length_x(date.getUTCMonth() + 1, 2)}/${number_length_x( 445 date.getUTCDate(), 446 2 447 )}/${date.getUTCFullYear()}${time_text}`; 448}; 449 450const dateify_timestamp = (ms, time = false) => { 451 452 var date = new Date(ms); 453 if (isNaN(date)) return false; 454 455 var time_text = "", 456 is_am = date.getHours() < 12, 457 hour = 458 date.getHours() > 12 459 ? date.getHours() - 12 460 : date.getHours() === 0 461 ? 12 462 : date.getHours(); 463 464 if (time) 465 time_text = ` at ${hour}:${number_length_x(date.getMinutes(), 2)} ${ 466 is_am ? "AM" : "PM" 467 }`; 468 469 return `${number_length_x(date.getMonth() + 1, 2)}/${number_length_x( 470 date.getDate(), 471 2 472 )}/${date.getFullYear()}${time_text}`; 473}; 474 475const prettify_difference = (t1,t2,allow_future = false) => { 476 let m1 = moment(t1), 477 m2 = moment(t2), 478 diff = m1.diff(m2), 479 pretty_diff = moment.duration(m1.diff(m2)).humanize(); 480 481 if (!allow_future && diff > 0) return "a few seconds ago"; 482 483 return `${pretty_diff} ${diff <= 0? "ago": "from now"}`; 484} 485 486/******************* 487 * Address Helpers * 488 *******************/ 489 490// Only works for: 491// (1) address_line1, address_line2, city, state zip(5) 492// (1) address_line1, city, state zip(5) 493function split_address(address_str) { 494 495 let address = { 496 address_line1: false, 497 address_line2: false, 498 city: false, 499 state: false, 500 zip: false, 501 }, 502 parts = address_str.split(", ").reverse(); 503 504 let [state, zip] = parts[0].split(" "); 505 zip = zip.trim(); 506 507 if (/^[0-9]{5}$/.test(zip)) 508 address.zip = zip; 509 510 if (validate_state_code(state)) 511 address.state = state.trim(); 512 513 address.city = parts[1].trim(); 514 address.address_line1 = parts[parts.length - 1].trim(); 515 516 if (parts.length > 3) address.address_line2 = parts[2]; 517 518 return address; 519} 520 521 522/**************** 523 * Date Helpers * 524 ****************/ 525 526Date.prototype.addDays = function(days) { 527 var date = new Date(this.valueOf()); 528 date.setDate(date.getDate() + days); 529 return date; 530} 531 532// Extend String.prototype 533String.prototype.titleCase = function () { 534 return this.replace(/\w\S*/g, function (txt) { 535 return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(); 536 }); 537}; 538 539String.prototype.snake_case = function () { 540 return this.toLowerCase().replace(/[ \t\n]+/g, "_"); 541} 542 543String.prototype.pluralize = function () { 544 return this + (/(s|sh|ch|x|z)$/.test(this) ? "(es)" : "(s)"); 545}; 546 547String.prototype.knownPluralize = function () { 548 return this + (/(s|sh|ch|x|z)$/.test(this) ? "es" : "s"); 549}; 550 551String.prototype.knownSingularize = function (lower = true) { 552 return (/^(a|e|i|o|u)/i.test(this) ? "an" : "a") + " " + (lower ? this.toLowerCase() : this); 553}; 554 555String.prototype.split_first = function (sep) { 556 let [first, ...rest] = this.split(sep); 557 return [first, rest.join(sep)]; 558}; 559 560String.prototype.escapeQuotes = function () { 561 return this.replace(/([\'\"])/g, "\\$1"); 562}; 563 564String.prototype.escape_html = function () { 565 return this 566 .replace(/&/g, "&") 567 .replace(/</g, "<") 568 .replace(/>/g, ">") 569 .replace(/"/g, """) 570 .replace(/'/g, "'"); 571}; 572 573String.prototype.unescape_html = function () { 574 return this 575 .replace(/&/g, "&") 576 .replace(/</g, "<") 577 .replace(/>/g, ">") 578 .replace(/"/g, "\"") 579 .replace(/'/g, "'"); 580}; 581 582 583String.prototype.symbolize = function () { 584 return this.toLowerCase().replace(/[^a-zA-Z0-9_]+/g, "_"); 585}; 586 587String.prototype.chunk = function(length,token = false,includetoken = false) { 588 589 if (!token) return this.match(new RegExp('.{1,' + length + '}', 'g')); 590 591 var string_arr = this.split(token), 592 arr = [""]; 593 594 string_arr.forEach( (part,i) => { 595 596 if( 597 i !== 0 598 && (arr[arr.length - 1].length || includetoken) 599 ) part = token + part; 600 601 arr[arr.length - 1] += part; 602 603 if(arr[arr.length - 1].length >= length && i !== string_arr.length) 604 arr.push(""); 605 }); 606 607 608 return arr; 609} 610 611 612String.prototype.escape_reg_exp = function(unescaped = []) { 613 const chars = Array.difference( 614 [".","*","+","?","^","$","{","}","(",")","|","[","]","\\"], 615 unescaped 616 ) 617 .join("") 618 .replace("\\","\\\\") 619 .replace("]","\\]"), 620 chars_reg_exp = new RegExp(`[${chars}]`,"g"); 621 622 return this.replace(chars_reg_exp, '\\$&'); 623} 624 625/* 626 Split string into chunks as designated by a list of percentages. Any unspeced 627 percentage will be added on as one final string in the array 628 629 the token argument allows you to specify a token that each part will be broken on (use " " 630 as your token to round percentage-chunks up to the nearest word) 631 632 i.e. 633 s = "This is a strong this is a string this is a good string this is definetly a good string this is a good test string this" 634 s.percentchunk([40,20,20]," ",true) 635 636 ==> ['This is a strong this is a string this is a good', ' string this is definetly', ' a good string this is a', ' good test string this'] 637*/ 638String.prototype.percentchunk = function(p_arr,token = "",includetoken = false) { 639 640 var percent_covered = p_arr.reduce( (sum,per) => (sum + per),0); 641 642 if (percent_covered > 100 || percent_covered <= 0) return null; 643 644 let char_arr = p_arr.map( (per) => Math.round(this.length * (per / 100)) ), 645 string_arr = this.split(token); 646 647 console.log(char_arr); 648 649 return string_arr.reduce( (chunks,part,i) => { 650 651 if(i !== 0 && includetoken) part = token + part; 652 653 if ( 654 chunks[chunks.length - 1].length >= char_arr[chunks.length - 1] 655 && i !== string_arr.length 656 ) 657 chunks.push(""); 658 659 else if (i !== 0 && !includetoken) part = token + part; 660 661 chunks[chunks.length - 1] += part; 662 663 return chunks; 664 665 }, [""]); 666} 667 668String.prototype.pretty_quotify = function() { 669 return this 670 .replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018") // opening singles 671 .replace(/'/g, "\u2019") // closing singles & apostrophes 672 .replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c") // opening doubles 673 .replace(/"/g, "\u201d"); // closing doubles 674}; 675 676Number.prototype.celsius_to_fahrenheit = function(round = 2) { 677 return roundify(this * (9/5) + 32, round); 678} 679 680/* JSON utilities */ 681 682const parse_valid_JSON = (str) => { 683 try { 684
685 var validated = JSON5.parse(str); 686 687 if (typeof validated == "object") 688 return validated; 689 690 } catch (e) {} 691 692 return false; 693}; 694 695/* Array utilities */ 696 697// Extend Array.prototype 698Array.intersection = (arr1, arr2) => arr1.filter((x) => arr2.includes(x)); 699Array.difference = (arr1, arr2) => arr1.filter((x) => !arr2.includes(x)); 700Array.symetric_difference = (arr1, arr2) => 701 arr1 702 .filter((x) => !arr2.includes(x)) 703 .concat(arr2.filter((x) => !arr1.includes(x))); 704Array.union = (arr1, arr2) => [...arr1, ...arr2]; 705 706Array.prototype.unique = function () { 707 var a = this.concat(); 708 return a.filter((val, i) => a.indexOf(val) === i); 709}; 710 711Array.prototype.average = function () { 712 return this.reduce((p, c) => p + c, 0) / this.length; 713}; 714 715Array.prototype.element_replace = function (f, r) { 716 var i = this.indexOf(f); 717 if (i === -1) return; 718 this[i] = r; 719}; 720 721Array.prototype.objectize = function (k) { 722 return this.reduce((obj,e) => { 723 if (typeof e == "object" && (k in e) ) { 724 let copy = {...e}; 725 delete copy[k]; 726 obj[ e[k] ] = copy; 727 } 728 return obj; 729 }, {}) 730} 731 732Array.prototype.find_last = function (cond) { 733 let filtered = this.filter(cond); 734 return filtered[filtered.length - 1]; 735} 736 737Array.distinct_json_array_push = (array,element,distinctattr) => { 738 if (!array.some((json) => json[distinctattr] === element[distinctattr])) 739 array.push(element); 740 return array; 741} 742 743Array.prototype.comma_separated_no_join = function (conjunction = "and", comma = ",") { 744 745 const filtered = this.filter(item => stripquotes(item)); 746 747 let needscommas = (filtered.length > 2); 748 749 return filtered.map((item,i) => { 750 item = stripquotes(item); 751 752 if (i != filtered.length - 1) { 753 if (needscommas) item = `${item}${comma}`; 754 if (i == filtered.length - 2) item = `${item} ${conjunction}`; 755 } 756 757 return item; 758 }); 759} 760 761Array.prototype.comma_separated = function(conjunction = "and", comma=",") { 762 return this.comma_separated_no_join(conjunction,comma).join(" "); 763} 764 765Array.prototype.smallest = function () { 766 return [...this].sort()[0]; 767} 768 769Array.prototype.pop_match = function (condition) { 770 let match = this.findIndex(condition); 771 return match !== -1 ? this.splice(match,1)[0] : undefined; 772} 773 774/* Sorts */ 775function numeric_list_tier_sort(nl1, nl2) { 776 777 // Same priority 778 if (!nl1?.length && !nl2.length) return 0; 779 780 // Same priority but nl1 is additionally nested 781 if (!nl2?.length) return 1; 782 783 // Same priority but nl2 is additionally nested 784 if (!nl1?.length) return -1; 785 786 const p_nl1 = Number(nl1.slice(-1)), 787 p_nl2 = Number(nl2.slice(-1)), 788 p_nl1_isnan = isNaN(p_nl1), 789 p_nl2_isnan = isNaN(p_nl2), 790 // If both are NaN or ===, we treat as equal priority 791 same_p = (p_nl1_isnan && p_nl2_isnan) || p_nl1 === p_nl2; 792 793 // If equal, we need to move up the list to determine priority 794 if (same_p) return numeric_list_tier_sort( 795 nl1.slice(0,nl1.length - 1), 796 nl2.slice(0,nl2.length - 1) 797 ); 798 799 // If nl1 is NaN, we deprioritize it 800 if (p_nl1_isnan) return 1; 801 802 // If nl2 is NaN, we deprioritize it 803 if (p_nl2_isnan) return -1; 804 805 // Use this tier as prioritization 806 return p_nl1 - p_nl2; 807} 808 809/* Promise utilities */ 810 811/** 812 * Wraps an async function so that only the most recent call's result is returned. 813 * Previous pending calls will resolve with `undefined` (or remain pending depending on implementation). 814 * 815 * @param {Function} async_fn - The async function to wrap 816 * @returns {Function} - The wrapped safe function 817 */ 818function debounce_promise(async_fn) { 819 let active_request_id = 0; 820 821 return async function(...args) { 822 // Capture the ID at the moment of the call 823 const request_id = ++active_request_id; 824 825 // Run the original function 826 const result = await async_fn.apply(this, args); 827 828 // If the ID has changed since we started, it means a newer call happened. 829 // We act as if this function never finished (return undefined). 830 if (request_id !== active_request_id) { 831 return; 832 } 833 834 return result; 835 }; 836} 837 838// Extend Promise 839Promise.all_values = async (object) => { 840 return _.zipObject(_.keys(object), await Promise.all(_.values(object))) 841} 842 843 844/* Spatial conversion */ 845 846const convert_to_wkt = (g) => { 847 var wkt = new Wkt.Wkt(); 848 wkt.read(JSON.stringify(g)); 849 return wkt.write(); 850}; 851 852/* Geometries */ 853 854// Get info 855const getCenter = (g) => turf.coordAll(turf.center(g))[0]; 856 857const point_on_feature = (feature) => { 858 var geometry = turf.lineToPolygon(turf.polygonToLine(feature)).geometry; 859 return turf.pointOnFeature(geometry); 860}; 861 862const center_of_feature = (feature) => { 863 var geometry = turf.lineToPolygon(turf.polygonToLine(feature)).geometry; 864 return turf.centroid(geometry); 865}; 866 867// Convert geometries 868const cornersToBBox = (c1, c2, properties = {}) => 869 turf.convex( 870 turf.polygon([[c1, [c1[0], c2[1]], c2, [c2[0], c1[1]], c1]], properties) 871 ); 872 873const latlngToBBox = (latlng) => 874 cornersToBBox( 875 [latlng._ne.lng, latlng._ne.lat], 876 [latlng._sw.lng, latlng._sw.lat] 877 ); 878 879/* Math */ 880 881// Comparative selection 882const smallestLargerThan = (obj, num) => { 883 var slt = obj[Math.min(...Object.keys(obj).filter((key) => key >= num))]; 884 885 return slt ? slt : obj[Math.max(...Object.keys(obj))]; 886}; 887 888const largestSmallerThan = (obj, num) => { 889 var lst = obj[Math.max(...Object.keys(obj).filter((key) => key <= num))]; 890 891 return lst ? lst : obj[Math.min(...Object.keys(obj))]; 892}; 893 894// Random 895function getRandomInt(max) { 896 return Math.floor(Math.random() * max); 897} 898 899// Round 900const roundify = (x, n) => Math.round(x * 10 ** n) / 10 ** n; 901 902 903/* JQUERY */ 904function append_at_index(parent,elem,index) { 905 if (index === 0)
906 parent.prepend(elem); 907 else if (parent.children().length <= index) 908 parent.append(elem); 909 else 910 parent.children(`*:nth-child(${index})`).after(elem); 911} 912 913 914/* Async */ 915 916function promiseState(promise, callback) { 917 // Symbols and RegExps are never content-equal 918 var uniqueValue = window['Symbol'] ? Symbol('unique') : /unique/ 919 920 function notifyPendingOrResolved(value) { 921 if (value === uniqueValue) { 922 return callback('pending') 923 } else { 924 return callback('fulfilled') 925 } 926 } 927 928 function notifyRejected(reason) { 929 return callback('rejected') 930 } 931 932 var race = [promise, Promise.resolve(uniqueValue)] 933 Promise.race(race).then(notifyPendingOrResolved, notifyRejected) 934} 935 936async function wrap_async_timer(asyncfn,timeoutfn,completefn,time, options = {}) { 937 938 var start = options.start || Date.now(), 939 resolved = false, 940 timeout = false, 941 promise = asyncfn().then(() => { 942 if(!timeout) resolved = true; 943 else completefn(); 944 }); 945 946 setTimeout( 947 () => { 948 if(!resolved) { 949 timeout = true; 950 timeoutfn(); 951 } 952 }, time 953 ); 954 955} 956 957async function waitForCondition(args, checkFn, err, options = {}) { 958 var max_time = options.max_time || 250, 959 wait_time = options.wait_time || 10, 960 iterativeFn = options.iterativeFn || false, 961 result = false;
962 963 await new Promise((resolve) => { 964 var start_time = Date.now(); 965 function checkFlag() { 966 try { 967 //args.length && 968 var check = checkFn(...args); 969 } catch (e) { 970 var check = false; 971 } 972 973 if (check) { 974 result = true; 975 resolve(); 976 } else if (Date.now() > start_time + max_time) { 977 // if(!options.fallback) console.log(err); 978 resolve(); 979 } else { 980 if (iterativeFn) args = [iterativeFn()]; 981 window.setTimeout(checkFlag, wait_time); 982 } 983 } 984 checkFlag(); 985 }); 986 987 return result; 988} 989 990const stop_event = (name, callback, additional = 0) => { 991 if (!STOP_EVENT[name]) STOP_EVENT[name] = 0; 992 993 STOP_EVENT[name] += 1; 994 var save_stop = STOP_EVENT[name]; 995 996 setTimeout(() => { 997 if (save_stop == STOP_EVENT[name]) { 998 try { 999 callback(); 1000 } catch (e) { 1001 if (additional) 1002 console.log("Error: you've selected this event too many times."); 1003 else stop_event(name, callback, STOP_EVENT_TIME); 1004 } 1005 STOP_EVENT[name] = 0; 1006 } 1007 }, STOP_EVENT_TIME + additional); 1008}; 1009
1010const delay = (ms) => new Promise((res) => setTimeout(res, ms)); 1011 1012async function ensure_takes_at_least(callback, min_time) { 1013 const pre_time = Date.now(), 1014 result = await callback(), 1015 time_taken = Date.now() - pre_time; 1016 1017 if (time_taken < min_time) await delay(min_time - time_taken); 1018 1019 return result; 1020}; 1021 1022function is_within_days_from_date(date_string, days) { 1023 return Math.abs((new Date(date_string) - new Date()) / (1000 * 3600 * 24)) <= days; 1024} 1025 1026function current_year_last_2_digits() { 1027 const currentYear = new Date().getFullYear(); 1028 return currentYear % 100; 1029} 1030 1031//dates should be in mm-dd-yyyy format in the provided timezone 1032function date_compare(date1, date2) { 1033 try { 1034 moment.suppressDeprecationWarnings = true; 1035 1036 const moment_date1 = moment(date1).unix(), 1037 moment_date2 = moment(date2).unix(); 1038 1039 return moment_date1 === moment_date2 1040 ? 0 1041 : moment_date1 > moment_date2 1042 ? 1 1043 : -1; 1044 } catch {} 1045 1046 return false; 1047} 1048 1049//date should be in mm-dd-yyyy format in the provided timezone 1050function date_compare_now(date, timezone = "UTC") { 1051 const now = new Date().toLocaleDateString("en-US", { 1052 timeZone: timezone, 1053 }); 1054 1055 return date_compare(date, now); 1056} 1057 1058/************************** 1059 * Parent / Child Windows * 1060 **************************/ 1061 1062function get_child_window() { 1063 return $(document).find(`#${CHILD_WINDOW_ID} > iframe`)[0].contentWindow; 1064} 1065 1066function has_child_window() { 1067 const child = $(document).find(`#${CHILD_WINDOW_ID} > iframe`); 1068 return Boolean(child && child.length) 1069} 1070 1071function run_across_windows(fn_str) { 1072 if (typeof window[fn_str] !== "function") return; 1073 window[fn_str](); 1074} 1075 1076/************** 1077 * State data * 1078 **************/ 1079 1080const STATES = { 1081 state_01: { abbreviation: "AL", name: "Alabama" }, 1082 state_31: { abbreviation: "NE", name: "Nebraska" }, 1083 state_02: { abbreviation: "AK", name: "Alaska" }, 1084 state_32: { abbreviation: "NV", name: "Nevada" }, 1085 state_04: { abbreviation: "AZ", name: "Arizona" }, 1086 state_33: { abbreviation: "NH", name: "New Hampshire" }, 1087 state_05: { abbreviation: "AR", name: "Arkansas" }, 1088 state_34: { abbreviation: "NJ", name: "New Jersey" }, 1089 state_06: { abbreviation: "CA", name: "California" }, 1090 state_35: { abbreviation: "NM", name: "New Mexico" }, 1091 state_08: { abbreviation: "CO", name: "Colorado" }, 1092 state_36: { abbreviation: "NY", name: "New York" }, 1093 state_09: { abbreviation: "CT", name: "Connecticut" }, 1094 state_37: { abbreviation: "NC", name: "North Carolina" }, 1095 state_10: { abbreviation: "DE", name: "Delaware" }, 1096 state_38: { abbreviation: "ND", name: "North Dakota" }, 1097 state_11: { abbreviation: "DC", name: "District of Columbia" }, 1098 state_39: { abbreviation: "OH", name: "Ohio" }, 1099 state_12: { abbreviation: "FL", name: "Florida" }, 1100 state_40: { abbreviation: "OK", name: "Oklahoma" }, 1101 state_13: { abbreviation: "GA", name: "Georgia" }, 1102 state_41: { abbreviation: "OR", name: "Oregon" }, 1103 state_15: { abbreviation: "HI", name: "Hawaii" }, 1104 state_42: { abbreviation: "PA", name: "Pennsylvania" }, 1105 state_16: { abbreviation: "ID", name: "Idaho" }, 1106 state_72: { abbreviation: "PR", name: "Puerto Rico" }, 1107 state_17: { abbreviation: "IL", name: "Illinois" }, 1108 state_44: { abbreviation: "RI", name: "Rhode Island" }, 1109 state_18: { abbreviation: "IN", name: "Indiana" }, 1110 state_45: { abbreviation: "SC", name: "South Carolina" }, 1111 state_19: { abbreviation: "IA", name: "Iowa" }, 1112 state_46: { abbreviation: "SD", name: "South Dakota" }, 1113 state_20: { abbreviation: "KS", name: "Kansas" }, 1114 state_47: { abbreviation: "TN", name: "Tennessee" }, 1115 state_21: { abbreviation: "KY", name: "Kentucky" }, 1116 state_48: { abbreviation: "TX", name: "Texas" }, 1117 state_22: { abbreviation: "LA", name: "Louisiana" }, 1118 state_49: { abbreviation: "UT", name: "Utah" }, 1119 state_23: { abbreviation: "ME", name: "Maine" }, 1120 state_50: { abbreviation: "VT", name: "Vermont" }, 1121 state_24: { abbreviation: "MD", name: "Maryland" }, 1122 state_51: { abbreviation: "VA", name: "Virginia" }, 1123 state_25: { abbreviation: "MA", name: "Massachusetts" }, 1124 state_78: { abbreviation: "VI", name: "Virgin Islands" }, 1125 state_26: { abbreviation: "MI", name: "Michigan" }, 1126 state_53: { abbreviation: "WA", name: "Washington" }, 1127 state_27: { abbreviation: "MN", name: "Minnesota" }, 1128 state_54: { abbreviation: "WV", name: "West Virginia" }, 1129 state_28: { abbreviation: "MS", name: "Mississippi" }, 1130 state_55: { abbreviation: "WI", name: "Wisconsin" }, 1131 state_29: { abbreviation: "MO", name: "Missouri" }, 1132 state_56: { abbreviation: "WY", name: "Wyoming" }, 1133 state_30: { abbreviation: "MT", name: "Montana" }, 1134 state_60: { abbreviation: "AS", name: "American Samoa" }, 1135 state_64: { abbreviation: "FM", name: "Federated States of Micronesia" }, 1136 state_66: { abbreviation: "GU", name: "Guam" }, 1137 state_68: { abbreviation: "MH", name: "Marshall Islands" }, 1138 state_69: { abbreviation: "MP", name: "Northern Mariana Islands" }, 1139 state_70: { abbreviation: "PW", name: "Palau" }, 1140}; 1141 1142const STATE_CODES = Object.values(STATES).map((state_info) => state_info.abbreviation).sort();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.