mirror of
https://github.com/xoseperez/espurna.git
synced 2026-03-03 15:04:17 +01:00
293 lines
6.3 KiB
JavaScript
293 lines
6.3 KiB
JavaScript
/**
|
|
* @param {string[]} rules
|
|
*/
|
|
export function styleInject(rules) {
|
|
if (!rules.length) {
|
|
return;
|
|
}
|
|
|
|
const style = document.createElement("style");
|
|
style.setAttribute("type", "text/css");
|
|
document.head.appendChild(style);
|
|
|
|
const sheet = style.sheet;
|
|
if (!sheet) {
|
|
return;
|
|
}
|
|
|
|
let pos = sheet.cssRules.length;
|
|
for (let rule of rules) {
|
|
style.sheet.insertRule(rule, pos++);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string} selector
|
|
* @param {boolean} value
|
|
*/
|
|
export function styleVisible(selector, value) {
|
|
return `${selector} { content-visibility: ${value ? "visible": "hidden"}; }`
|
|
}
|
|
|
|
/**
|
|
* @param {number} timeout
|
|
*/
|
|
export function pageReloadIn(timeout) {
|
|
setTimeout(() => window.location.reload(), timeout);
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} container
|
|
*/
|
|
function moreElem(container) {
|
|
container.querySelectorAll(".more")
|
|
.forEach((elem) => {
|
|
if (!(elem instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
|
|
elem.style.display = (elem.style.display === "")
|
|
? "inherit" : "";
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {Event} event
|
|
*/
|
|
export function onMoreParent(event) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
|
|
const target = event.target;
|
|
if (!(target instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
|
|
const parent = target?.parentElement?.parentElement;
|
|
if (parent) {
|
|
moreElem(parent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Element | DocumentFragment} container
|
|
*/
|
|
export function moreParent(container) {
|
|
for (let elem of container.querySelectorAll("button.button-more-parent")) {
|
|
elem.addEventListener("click", onMoreParent);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} elem
|
|
*/
|
|
export function lastMoreElem(elem) {
|
|
if (elem.lastChild instanceof HTMLElement) {
|
|
moreElem(elem.lastChild)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} elem
|
|
*/
|
|
function menuToggle(elem) {
|
|
elem.classList.toggle("active");
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} elem
|
|
*/
|
|
function menuHide(elem) {
|
|
elem.classList.remove("active");
|
|
}
|
|
|
|
/**
|
|
* @param {Event} event
|
|
* @returns {any}
|
|
*/
|
|
export function onMenuLinkClick(event) {
|
|
event.preventDefault();
|
|
|
|
const target = event.target;
|
|
if (!(target instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
|
|
if (target.parentElement) {
|
|
menuToggle(target.parentElement);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} elem
|
|
*/
|
|
export function showPanel(elem) {
|
|
if (elem.style.display !== "revert") {
|
|
for (const panel of document.getElementsByClassName("panel")) {
|
|
if (!(panel instanceof HTMLElement)) {
|
|
continue;
|
|
}
|
|
|
|
panel.style.display = "none";
|
|
}
|
|
|
|
elem.style.display = "revert";
|
|
}
|
|
|
|
const layout = document.getElementById("layout")
|
|
if (layout) {
|
|
menuHide(layout);
|
|
}
|
|
|
|
// TODO: sometimes, switching view causes us to scroll past
|
|
// the header (e.g. emon ratios panel on small screen)
|
|
// layout itself stays put, but the root element seems to scroll,
|
|
// at least can be reproduced with Chrome
|
|
if (document.documentElement) {
|
|
document.documentElement.scrollTop = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} elem
|
|
* @param {function(HTMLElement): void} callback
|
|
*/
|
|
export function findPanel(elem, callback) {
|
|
const panel = elem.closest(".panel");
|
|
if (!(panel instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
|
|
callback(panel);
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
*/
|
|
export function showPanelByName(name) {
|
|
// only a single panel is shown on the 'layout'
|
|
const panel = document.getElementById(`panel-${name}`);
|
|
if (!panel) {
|
|
return;
|
|
}
|
|
|
|
showPanel(panel);
|
|
}
|
|
|
|
/**
|
|
* @param {Event} event
|
|
*/
|
|
export function onPanelTargetClick(event) {
|
|
event.preventDefault();
|
|
|
|
const target = event.target;
|
|
if (!(target instanceof HTMLElement)) {
|
|
return;
|
|
}
|
|
|
|
const name = target.dataset["panel"];
|
|
if (name) {
|
|
showPanelByName(name);
|
|
}
|
|
|
|
panelTargetShowSelected(target);
|
|
}
|
|
|
|
/**
|
|
* @param {HTMLElement} elem
|
|
*/
|
|
function panelTargetShowSelected(elem) {
|
|
const root = elem.closest("#menu");
|
|
if (!root) {
|
|
return;
|
|
}
|
|
|
|
const parent = elem.parentElement;
|
|
if (!parent) {
|
|
return;
|
|
}
|
|
|
|
parent.classList.add("pure-menu-selected");
|
|
|
|
/** @type {NodeListOf<HTMLAnchorElement>} */
|
|
(root.querySelectorAll("#menu a[data-panel]"))
|
|
.forEach((a) => {
|
|
if (a.parentElement && a.parentElement !== parent) {
|
|
a.parentElement.classList.remove("pure-menu-selected");
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @typedef {{hex?: boolean, lowercase?: boolean, numbers?: boolean, special?: boolean, uppercase?: boolean}} RandomStringOptions
|
|
*
|
|
* @param {number} length
|
|
* @param {RandomStringOptions} options
|
|
*/
|
|
export function randomString(length, {hex = false, lowercase = true, numbers = true, special = false, uppercase = true} = {}) {
|
|
let mask = "";
|
|
if (lowercase || hex) { mask += "abcdef"; }
|
|
if (lowercase) { mask += "ghijklmnopqrstuvwxyz"; }
|
|
if (uppercase || hex) { mask += "ABCDEF"; }
|
|
if (uppercase) { mask += "GHIJKLMNOPQRSTUVWXYZ"; }
|
|
if (numbers || hex) { mask += "0123456789"; }
|
|
if (special) { mask += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; }
|
|
|
|
const source = new Uint32Array(length);
|
|
const result = new Array(length);
|
|
|
|
window.crypto
|
|
.getRandomValues(source)
|
|
.forEach((value, i) => {
|
|
result[i] = mask[value % mask.length];
|
|
});
|
|
|
|
return result.join("");
|
|
}
|
|
|
|
/**
|
|
* @throws {Error}
|
|
* @param {boolean} value
|
|
* @param {string} message
|
|
* @returns {asserts value}
|
|
*/
|
|
export function assert(value, message = "") {
|
|
if (!value) {
|
|
throw new Error(message ?? "assertion failed");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @template T
|
|
* @param {T[]} values
|
|
* @param {function(T): boolean} callback
|
|
* @returns {number}
|
|
*/
|
|
export function count(values, callback) {
|
|
return values.filter(callback).length;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {string}
|
|
*/
|
|
export function capitalize(value) {
|
|
return value === ""
|
|
? value
|
|
: `${value.charAt(0).toUpperCase()}${value.slice(1)}`;
|
|
}
|
|
|
|
/**
|
|
* @param {string} value
|
|
* @returns {boolean}
|
|
*/
|
|
export function stringToBoolean(value) {
|
|
return [
|
|
"1",
|
|
"y",
|
|
"yes",
|
|
"true",
|
|
"on",
|
|
].includes(value.toLowerCase());
|
|
}
|