/* Copyright (c) 2023-forever Douglas Malnati. All rights reserved. See the /faq/tos page for details. (If this generated header is stamped on a file which is a 3rd party file or under a different license or copyright, then ignore this copyright statement and use that file's terms.) */ const INTL_NUMBER_FORMAT_EN_US = new Intl.NumberFormat("en-US"); export class StrAccumulator { constructor() { this.str = ``; this.indent = 0; this.sep = ""; } A(appendStr) { this.str = `${this.str}${this.sep}${" ".repeat(this.indent)}${appendStr}`; this.sep = "\n"; } IncrIndent() { this.indent += 4; } DecrIndent() { this.indent -= 4; } Get() { return this.str; } } export function Commas(num) { if (num == null) { return num; } if (typeof num == "number") { if (!Number.isFinite(num)) { return `${num}`; } let wholePart = Math.trunc(num); let wholeStr = INTL_NUMBER_FORMAT_EN_US.format(wholePart); if (Number.isInteger(num)) { return wholeStr; } let numStr = `${num}`; let dotIdx = numStr.indexOf("."); if (dotIdx == -1) { return wholeStr; } return wholeStr + numStr.substring(dotIdx); } if (typeof num == "string") { let str = num.trim(); if (str == "") { return str; } let sign = ""; if (str[0] == "-" || str[0] == "+") { sign = str[0]; str = str.substring(1); } let dotIdx = str.indexOf("."); let wholePart = dotIdx == -1 ? str : str.substring(0, dotIdx); let fracPart = dotIdx == -1 ? "" : str.substring(dotIdx); if (!/^\d+$/.test(wholePart)) { return num; } let wholeStr = INTL_NUMBER_FORMAT_EN_US.format(Number(wholePart)); return sign + wholeStr + fracPart; } return `${num}`; } // take in any css color string (hex, rgb, name, etc) // return array [r, g, b] export function GetRgbFromColor(color) { let d = document.createElement("div"); d.style.color = color; document.body.appendChild(d); // comes out as "rgb(r, g, b)" let rgbStrColor = window.getComputedStyle(d).color; document.body.removeChild(d); // extract r, g, b let rgbArr = rgbStrColor.substring(4, rgbStrColor.length-1).replace(/ /g, '').split(','); rgbArr[0] = parseInt(rgbArr[0]); rgbArr[1] = parseInt(rgbArr[1]); rgbArr[2] = parseInt(rgbArr[2]); return rgbArr; } // num1 can be higher/lower/equal to num2 // pct is a decimal value, so 50 means 50% // return a number that is pct between num1 and num2 export function PctBetween(pct, num1, num2) { if (pct < 0) { pct = 0; } if (pct > 100) { pct = 100; } let min = Math.min(num1, num2); let max = Math.max(num1, num2); let diff = max - min; if (diff < 0) { diff = -diff; } let retVal = min + ((pct / 100) * diff); return retVal; } // returns a string of "rgb(r, g, b)" export function ColorPctBetween(pct, colorStart, colorEnd, toInt = false) { let rgbArrStart = GetRgbFromColor(colorStart); let rgbArrEnd = GetRgbFromColor(colorEnd); let r = PctBetween(pct, rgbArrStart[0], rgbArrEnd[0]); let g = PctBetween(pct, rgbArrStart[1], rgbArrEnd[1]); let b = PctBetween(pct, rgbArrStart[2], rgbArrEnd[2]); if (toInt) { r = Math.round(r); g = Math.round(g); b = Math.round(b); } let retVal = `rgb(${r}, ${g}, ${b})`; return retVal; } export function Now() { return Date.now(); } export function DateYYYY() { return `${(new Date()).getFullYear()}`; } export function DateMM() { return String((new Date()).getMonth() + 1).padStart(2, '0'); } export function DateDD() { return String((new Date()).getDate()).padStart(2, '0'); } export function TimeHH() { return String((new Date()).getHours()).padStart(2, '0'); } export function TimeMM() { return String((new Date()).getMinutes()).padStart(2, '0'); } export function TimeSS() { return String((new Date()).getSeconds()).padStart(2, '0'); } export function ValOrDefaultFn(str, fn) { let retVal; if (str) { str = String(str).trim(); if (str == "") { retVal = fn(); } else { retVal = str; } } else { retVal = fn(); } return retVal; } export function ValOrDefault(str, strDefault) { let retVal; if (str) { str = String(str).trim(); if (str == "") { retVal = strDefault; } else { retVal = str; } } else { retVal = strDefault; } return retVal; } export function ParseDateTimeToIsoString(timeStr) { timeStr = timeStr.trim(); let timePartArr = timeStr.trim().split(" "); let YYYY, MM, DD; let hh, mm, ss; if (timePartArr.length >= 1) { let date = timePartArr[0]; let datePartList = date.split("-"); if (datePartList.length >= 1) { YYYY = datePartList[0]; } if (datePartList.length >= 2) { MM = datePartList[1].padStart(2, '0'); } else { MM = "01"; } if (datePartList.length >= 3) { DD = datePartList[2].padStart(2, '0'); } else { DD = "01"; } } if (timePartArr.length >= 2) { let time = timePartArr[1]; let timePartList = time.split(":"); if (timePartList.length >= 1) { hh = timePartList[0].padStart(2, '0'); } else { hh = "00"; } if (timePartList.length >= 2) { mm = timePartList[1].padStart(2, '0'); } else { mm = "00"; } if (timePartList.length >= 3) { ss = timePartList[2].padStart(2, '0'); } else { ss = "00"; } } YYYY = ValOrDefault(YYYY, DateYYYY()); MM = ValOrDefault(MM, "01"); DD = ValOrDefault(DD, "00"); hh = ValOrDefault(hh, "00"); mm = ValOrDefault(mm, "00"); ss = ValOrDefault(ss, "00"); let timeStrIso = `${YYYY}-${MM}-${DD}T${hh}:${mm}:${ss}`; return timeStrIso; } export function ConvertUtcToLocal(timeStr) { let isoStr = ParseDateTimeToIsoString(timeStr); isoStr += "Z"; let ms = (new Date(isoStr)).getTime(); let localTimeStr = MakeDateTimeFromMs(ms); return localTimeStr; } export function ConvertLocalToUtc(timeStr) { // we have a local time, what is that in UTC? // it is as different to us as we are from it // first, get the local time in ms let isoStr = ParseDateTimeToIsoString(timeStr); let msLocal = (new Date(isoStr)).getTime(); // next, get that same local time in ms, but pretend we're in UTC let isoStrUtc = isoStr + "Z"; let msUtcTmp = (new Date(isoStrUtc)).getTime(); // look at the difference let msDiff = msUtcTmp - msLocal; // calculate let msUtc = msLocal - msDiff; // convert to str let utcTimeStr = MakeDateTimeFromMs(msUtc); return utcTimeStr; } export function ParseTimeToMs(timeStr) { let timeStrIso = ParseDateTimeToIsoString(timeStr); let ms = Date.parse(timeStrIso); return ms; } export function MsDiff(time1, time2) { let msTime1 = ParseTimeToMs(time1); let msTime2 = ParseTimeToMs(time2); return msTime1 - msTime2; } export function MsUntil(timeStr) { let msNow = Now(); let msThen = ParseTimeToMs(timeStr); let msUntil = msThen - msNow; if (timeStr == "") { msUntil = 0; } return msUntil; } export function MsUntilDate(dateStr) { let msNow = ParseTimeToMs(MakeDateFromMs(Now())); let msThen = ParseTimeToMs(dateStr); let msUntil = msThen - msNow; if (dateStr == "") { msUntil = 0; } return msUntil; } export function DateTimeValid(timeStr) { return isNaN(ParseTimeToMs(timeStr)) == false; } export function MakeDateTimeFromDateTime(timeStr) { let timeStrIso = ParseDateTimeToIsoString(timeStr); // drop the T let partList = timeStrIso.split("T"); let timeStrIsoNoT = `${partList[0]} ${partList[1]}`; return timeStrIsoNoT; } export function MakeDateFrom(timeStr) { let dateTime = MakeDateTimeFromDateTime(timeStr); return dateTime.split(" ")[0]; } export function MakeDateTimeFromMs(ms) { let dt = new Date(ms); let YYYY = dt.getFullYear(); let MM = String((dt.getMonth() + 1)).padStart(2, '0') let DD = String(dt.getDate()).padStart(2, '0') let hh = String(dt.getHours()).padStart(2, '0') let mm = String(dt.getMinutes()).padStart(2, '0') let ss = String(dt.getSeconds()).padStart(2, '0') let dateTime = `${YYYY}-${MM}-${DD} ${hh}:${mm}:${ss}`; return dateTime; } export function MakeDateTimeNow() { return MakeDateTimeFromMs(Now()); } export function MakeDateFromMs(ms) { let dateTime = MakeDateTimeFromMs(ms); return dateTime.split(" ")[0]; } // only return the non-zero elements // eg 3 days, 0 hours, 5 mins = 3 days, 0 hours, 5 mins // eg 0 days, 0 hours, 5 mins = 5 mins // eg 0 days, 0 hours, 0 mins = 0 mins export function DurationStrTrim(str) { let partList = str.split(","); let partListNew = []; let keepRemaining = false; for (let i = 0; i < partList.length; ++i) { let part = partList[i].trim(); if (keepRemaining) { partListNew.push(partList[i]); } else if (part.charAt(0) != "0") { partListNew.push(partList[i]); keepRemaining = true; } else { // do nothing, drop } } if (partListNew.length == 0) { partListNew.push(partList[partList.length - 1]); } return partListNew.join(",").trim(); } export function MsToDurationStrDaysHoursMinutes(ms) { let full = MsToDurationStrDaysHoursMinutesSeconds(ms); return full.split(",").slice(0, 3).join(",").trim(); } export function MsToDurationStrMinutes(ms) { let full = MsToDurationStrDaysHoursMinutesSeconds(ms); return full.split(",").slice(2, 3).join(",").trim(); } export function MsToDurationStrDaysHoursMinutesSeconds(ms) { let val = ms; // ignore ms val = Math.floor(val / 1000); // how many days? let days = Math.floor(val / (24 * 60 * 60)); val -= days * (24 * 60 * 60); // how many hours? let hours = Math.floor(val / (60 * 60)); val -= hours * (60 * 60); // how many minutes? let mins = Math.floor(val / 60); val -= mins * 60; // how many seconds? let secs = val; let duration = ""; duration += `${days}` + (days == 1 ? " day" : " days"); duration += ", "; duration += `${hours}` + (hours == 1 ? " hour" : " hours"); duration += ", "; duration += `${mins}` + (mins == 1 ? " min" : " mins"); duration += ", "; duration += `${secs}` + (secs == 1 ? " sec" : " secs"); return duration; } export function Rotate(arr, count) { let retVal = [... arr]; if (count > 0) { while (count) { retVal.unshift(retVal.pop()); --count; } } else if (count < 0) { count = -count; while (count) { retVal.push(retVal.shift()); --count; } } return retVal; } export function ListIntersectionUnique(leftList, rightList) { let retVal = []; if (leftList.length == 0) { retVal = rightList; } else if (rightList.length == 0) { retVal = leftList; } else { let m = new Map(); for (const left of leftList) { m.set(left, 1); } for (const right of rightList) { if (m.has(right)) { m.set(right, 2); } } for (let [key, val] of m) { if (val == 2) { retVal.push(key); } } } retVal.sort(); return retVal; } export function GetSearchParam(paramName, defaultValue) { let retVal = defaultValue; const params = new URLSearchParams(window.location.search); if (params.has(paramName)) { let val = params.get(paramName); if (val != "null" && val != undefined && val != "undefined") { retVal = val.trim(); } } return retVal; } export function SetDomValBySearchParam(dom, paramName, defaultValue) { const params = new URLSearchParams(window.location.search); dom.value = GetSearchParam(paramName, defaultValue); } export function SetDomCheckedBySearchParam(dom, paramName) { const params = new URLSearchParams(window.location.search); dom.checked = params.get(paramName) != "0"; } export function StrToCssClassName(col) { return col.replace(/[.\s]/g, '_'); }; export function MakeTable(dataTable, synthesizeRowCountColumn) { // build table let table = document.createElement("table"); let rowHeader = dataTable[0]; let headerClassDataList = rowHeader.map(colVal => { let colClass = ""; let hdrClass = ""; let dataClass = ""; try { colClass = StrToCssClassName(`${colVal}_col`); hdrClass = StrToCssClassName(`${colVal}_hdr`); dataClass = StrToCssClassName(`${colVal}_data`); } catch (e) { } return { colClass, hdrClass, dataClass, }; }); let EscapeHtml = (val) => { return String(val) .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """); }; let MakeClassAttr = (classList) => { let filtered = classList.filter(str => str != ""); if (filtered.length == 0) { return ""; } return ` class="${filtered.join(" ")}"`; }; let SetTableCellContent = (dom, val) => { if (val instanceof Element) { dom.appendChild(val); } else if (typeof val == "string" && val.indexOf("<") != -1) { dom.innerHTML = val; } else { dom.textContent = val ?? ""; } }; // build column group which allows styling to affect the entire column if desired // https://stackoverflow.com/questions/27234480/can-i-color-table-columns-using-css-without-coloring-individual-cells let colgroup = document.createElement("colgroup"); if (synthesizeRowCountColumn) { let col = document.createElement("col"); col.classList.add(StrToCssClassName("row_col")); colgroup.appendChild(col); } for (let idx = 0; idx < rowHeader.length; ++idx) { let col = document.createElement("col"); let colClass = headerClassDataList[idx].colClass; if (colClass != "") { col.classList.add(colClass); } colgroup.appendChild(col); } table.appendChild(colgroup); // build header let thead = document.createElement("thead"); let trHeader = document.createElement("tr"); trHeader.classList.add(StrToCssClassName("headerRow")); if (synthesizeRowCountColumn) { let thRow = document.createElement("th"); thRow.innerHTML = "row"; thRow.classList.add(StrToCssClassName("row_col")); thRow.classList.add(StrToCssClassName(`row_hdr`)); trHeader.appendChild(thRow); } for (let idx = 0; idx < rowHeader.length; ++idx) { const colVal = rowHeader[idx]; let th = document.createElement("th"); th.textContent = colVal; let colClass = headerClassDataList[idx].colClass; let hdrClass = headerClassDataList[idx].hdrClass; if (colClass != "") { th.classList.add(colClass); } if (hdrClass != "") { th.classList.add(hdrClass); } trHeader.appendChild(th); } thead.appendChild(trHeader); table.appendChild(thead); // build body let tbody = document.createElement("tbody"); let useFastStringPath = true; for (let i = 1; i < dataTable.length && useFastStringPath; ++i) { for (const colVal of dataTable[i]) { if (colVal instanceof Element) { useFastStringPath = false; break; } } } if (useFastStringPath) { let html = ""; for (let i = 1; i < dataTable.length; ++i) { html += "