app/util.mjs (102 lines of code) (raw):
export function _(parentOrSelector, selector) {
return selector
? parentOrSelector.querySelector(selector)
: document.querySelector(parentOrSelector);
}
export function __(parentOrSelector, selector) {
return selector
? parentOrSelector.querySelectorAll(selector)
: document.querySelectorAll(parentOrSelector);
}
export function setLoadingStage(stage) {
setTimeout(() => {
_("#loading-stage").textContent = `Loading ${stage}`;
}, 0);
}
export function debounce(targetFunction, wait) {
let timeout;
return () => {
const originalArguments = arguments;
const later = () => {
timeout = undefined;
targetFunction.apply(undefined, originalArguments);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
export function cloneTemplate($template) {
return document.importNode($template.content, true);
}
export function updateTemplate($content, data) {
// simple templating engine built on data- attributes
// data-field -> set textContent
// data-href-field -> set href attribute
// data-title-field -> set title attribute
// data-id-field -> set id attribute
// eg.
// <div data-field="name"></div> + {"name": "bob"}
// --> <div data-field="name">bob</div>
for (const [name, value] of Object.entries(data)) {
for (const $el of __($content, `*[data-field=${name}]`)) {
$el.textContent = value;
}
for (const field of ["href", "title", "id"]) {
for (const $el of __($content, `*[data-${field}-field=${name}]`)) {
$el.setAttribute(field, value);
}
}
}
}
export function timeAgo(timestamp) {
const ss = Math.round(Date.now() - timestamp) / 1000;
const mm = Math.round(ss / 60);
const hh = Math.round(mm / 60);
const dd = Math.round(hh / 24);
const mo = Math.round(dd / 30);
const yy = Math.round(mo / 12);
if (ss < 10) return "Just now";
if (ss < 45) return ss + " seconds ago";
if (ss < 90) return "1 minute ago";
if (mm < 45) return mm + " minutes ago";
if (mm < 90) return "1 hour ago";
if (hh < 24) return hh + " hours ago";
if (hh < 36) return "1 day ago";
if (dd < 30) return dd + " days ago";
if (dd < 45) return "1 month ago";
if (mo < 12) return mo + " months ago";
if (mo < 18) return "1 year ago";
return yy + " years ago";
}
export function hashCode(s) {
// https://stackoverflow.com/a/15710692/953
// eslint-disable-next-line unicorn/no-array-reduce
return s.split("").reduce((a, b) => {
a = (a << 5) - a + b.codePointAt(0);
return a & a;
}, 0);
}
export function chunked(list, size) {
// eslint-disable-next-line unicorn/new-for-builtins
return [...Array(Math.ceil(list.length / size))].map((_, i) =>
list.slice(i * size, i * size + size),
);
}
export function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
export function localiseNumbers(vars) {
for (const field of Object.keys(vars)) {
const value = vars[field];
if (typeof value === "number" && Number.isFinite(value)) {
vars[field] = value.toLocaleString(undefined, {
maximumFractionDigits: 2,
trailingZeroDisplay: "stripIfInteger",
});
}
}
}