mirror of
https://github.com/xoseperez/espurna.git
synced 2026-03-12 11:17:06 +01:00
webui: more handlers typings
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* @param {string[]} rules
|
||||
*/
|
||||
export function styleInject(rules) {
|
||||
if (!rules.length) {
|
||||
return;
|
||||
@@ -7,23 +10,30 @@ export function styleInject(rules) {
|
||||
style.setAttribute("type", "text/css");
|
||||
document.head.appendChild(style);
|
||||
|
||||
let pos = style.sheet.cssRules.length;
|
||||
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} ms
|
||||
* @param {number} timeout
|
||||
*/
|
||||
export function pageReloadIn(ms) {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, parseInt(ms, 10));
|
||||
export function pageReloadIn(timeout) {
|
||||
setTimeout(window.location.reload, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,11 +51,9 @@ export function moreElem(container) {
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleMenu(event) {
|
||||
event.preventDefault();
|
||||
event.target.parentElement.classList.toggle("active");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
*/
|
||||
export function showPanelByName(name) {
|
||||
// only a single panel is shown on the 'layout'
|
||||
const target = document.getElementById(`panel-${name}`);
|
||||
@@ -53,13 +61,20 @@ export function showPanelByName(name) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const panel of document.querySelectorAll(".panel")) {
|
||||
for (const panel of document.getElementsByClassName("panel")) {
|
||||
if (!(panel instanceof HTMLElement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
panel.style.display = "none";
|
||||
}
|
||||
|
||||
target.style.display = "revert";
|
||||
|
||||
const layout = document.getElementById("layout");
|
||||
layout.classList.remove("active");
|
||||
if (layout) {
|
||||
layout.classList.remove("active");
|
||||
}
|
||||
|
||||
// TODO: sometimes, switching view causes us to scroll past
|
||||
// the header (e.g. emon ratios panel on small screen)
|
||||
@@ -70,32 +85,43 @@ export function showPanelByName(name) {
|
||||
}
|
||||
}
|
||||
|
||||
export function showPanel(event) {
|
||||
/**
|
||||
* @param {Event} event
|
||||
*/
|
||||
export function onPanelTargetClick(event) {
|
||||
event.preventDefault();
|
||||
showPanelByName(event.target.dataset["panel"]);
|
||||
}
|
||||
|
||||
export function randomString(length, args) {
|
||||
if (typeof args === "undefined") {
|
||||
args = {
|
||||
lowercase: true,
|
||||
uppercase: true,
|
||||
numbers: true,
|
||||
special: true
|
||||
}
|
||||
const target = event.target;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const name = target.dataset["panel"];
|
||||
if (name) {
|
||||
showPanelByName(name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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 (args.lowercase) { mask += "abcdefghijklmnopqrstuvwxyz"; }
|
||||
if (args.uppercase) { mask += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; }
|
||||
if (args.numbers || args.hex) { mask += "0123456789"; }
|
||||
if (args.hex) { mask += "ABCDEF"; }
|
||||
if (args.special) { 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 += "~`!@#$%^&*()_+-={}[]:\";'<>?,./|\\"; }
|
||||
|
||||
let source = new Uint32Array(length);
|
||||
let result = new Array(length);
|
||||
const source = new Uint32Array(length);
|
||||
const result = new Array(length);
|
||||
|
||||
window.crypto.getRandomValues(source)
|
||||
window.crypto
|
||||
.getRandomValues(source)
|
||||
.forEach((value, i) => {
|
||||
result[i] = mask[value % mask.length];
|
||||
});
|
||||
|
||||
@@ -6,21 +6,18 @@ window.onerror = notifyError;
|
||||
import {
|
||||
pageReloadIn,
|
||||
randomString,
|
||||
showPanel,
|
||||
onPanelTargetClick,
|
||||
styleInject,
|
||||
} from './core.mjs';
|
||||
|
||||
import { validatePassword, validateFormsPasswords } from './validate.mjs';
|
||||
|
||||
import {
|
||||
askAndCallAction,
|
||||
askAndCallReboot,
|
||||
askAndCallReconnect,
|
||||
} from './question.mjs';
|
||||
import { askAndCall } from './question.mjs';
|
||||
|
||||
import {
|
||||
init as initSettings,
|
||||
applySettings,
|
||||
askSaveSettings,
|
||||
getData,
|
||||
setChangedElement,
|
||||
updateVariables,
|
||||
@@ -29,7 +26,11 @@ import {
|
||||
|
||||
import { init as initWiFi } from './wifi.mjs';
|
||||
import { init as initGpio } from './gpio.mjs';
|
||||
import { init as initConnection, connect } from './connection.mjs';
|
||||
import {
|
||||
init as initConnection,
|
||||
connect,
|
||||
sendAction,
|
||||
} from './connection.mjs';
|
||||
|
||||
import { init as initApi } from './api.mjs';
|
||||
import { init as initCurtain } from './curtain.mjs';
|
||||
@@ -221,8 +222,52 @@ function keepTime() {
|
||||
}
|
||||
}
|
||||
|
||||
/** @import { QuestionWrapper } from './question.mjs' */
|
||||
|
||||
/** @type {QuestionWrapper} */
|
||||
function askDisconnect(ask) {
|
||||
return ask("Are you sure you want to disconnect from the current WiFi network?");
|
||||
}
|
||||
|
||||
/** @type {QuestionWrapper} */
|
||||
function askReboot(ask) {
|
||||
return ask("Are you sure you want to reboot the device?");
|
||||
}
|
||||
|
||||
function askAndCallReconnect() {
|
||||
askAndCall([askSaveSettings, askDisconnect], () => {
|
||||
sendAction("reconnect");
|
||||
});
|
||||
}
|
||||
|
||||
function askAndCallReboot() {
|
||||
askAndCall([askSaveSettings, askReboot], () => {
|
||||
sendAction("reboot");
|
||||
});
|
||||
}
|
||||
|
||||
/** @param {Event} event */
|
||||
function askAndCallSimpleAction(event) {
|
||||
const target = event.target;
|
||||
if (!(target instanceof HTMLButtonElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {QuestionWrapper} */
|
||||
const wrapper =
|
||||
(ask) => ask(`Confirm the action: "${target.textContent}"`);
|
||||
|
||||
askAndCall([wrapper], () => {
|
||||
sendAction(target.name);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {import("./settings.mjs").KeyValueListeners}
|
||||
* @import { KeyValueListeners } from './settings.mjs'
|
||||
*/
|
||||
|
||||
/**
|
||||
* @returns {KeyValueListeners}
|
||||
*/
|
||||
function listeners() {
|
||||
return {
|
||||
@@ -298,17 +343,32 @@ function initSetupPassword(form) {
|
||||
* @param {Event} event
|
||||
* @returns {any}
|
||||
*/
|
||||
function toggleMenu(event) {
|
||||
function onMenuLinkClick(event) {
|
||||
event.preventDefault();
|
||||
/** @type {HTMLElement} */(event.target).parentElement?.classList.toggle("active");
|
||||
|
||||
const target = event.target;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target?.parentElement) {
|
||||
target.parentElement.classList.toggle("active");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event
|
||||
*/
|
||||
function toggleVisiblePassword(event) {
|
||||
const target = /** @type {HTMLSpanElement} */(event.target);
|
||||
const input = /** @type {HTMLInputElement} */(target.previousElementSibling);
|
||||
function onPasswordRevealClick(event) {
|
||||
const target = event.target;
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const input = target.previousElementSibling;
|
||||
if (!(input instanceof HTMLInputElement)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.type === "password") {
|
||||
input.type = "text";
|
||||
@@ -348,15 +408,15 @@ function init() {
|
||||
|
||||
document.querySelectorAll(".password-reveal")
|
||||
.forEach((elem) => {
|
||||
elem.addEventListener("click", toggleVisiblePassword);
|
||||
elem.addEventListener("click", onPasswordRevealClick);
|
||||
});
|
||||
|
||||
// Sidebar menu & buttons
|
||||
document.querySelector(".menu-link")
|
||||
?.addEventListener("click", toggleMenu);
|
||||
?.addEventListener("click", onMenuLinkClick);
|
||||
document.querySelectorAll(".pure-menu-link")
|
||||
.forEach((elem) => {
|
||||
elem.addEventListener("click", showPanel);
|
||||
elem.addEventListener("click", onPanelTargetClick);
|
||||
});
|
||||
|
||||
document.querySelector(".button-reconnect")
|
||||
@@ -369,7 +429,7 @@ function init() {
|
||||
// Generic action sender
|
||||
document.querySelectorAll(".button-simple-action")
|
||||
.forEach((elem) => {
|
||||
elem.addEventListener("click", askAndCallAction);
|
||||
elem.addEventListener("click", askAndCallSimpleAction);
|
||||
});
|
||||
|
||||
variableListeners(listeners());
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import { pendingChanges } from './settings.mjs';
|
||||
import { sendAction } from './connection.mjs';
|
||||
/**
|
||||
* @typedef {function(string): boolean} Question
|
||||
*/
|
||||
|
||||
export function askSaveSettings(ask) {
|
||||
if (pendingChanges()) {
|
||||
return ask("There are pending changes to the settings, continue the operation without saving?");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
export function askDisconnect(ask) {
|
||||
return ask("Are you sure you want to disconnect from the current WiFi network?");
|
||||
}
|
||||
|
||||
export function askReboot(ask) {
|
||||
return ask("Are you sure you want to reboot the device?");
|
||||
}
|
||||
/**
|
||||
* @typedef {function(Question): boolean} QuestionWrapper
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {QuestionWrapper[]} questions
|
||||
* @param {function(): void} callback
|
||||
*/
|
||||
export function askAndCall(questions, callback) {
|
||||
for (let question of questions) {
|
||||
if (!question(window.confirm)) {
|
||||
@@ -26,22 +19,3 @@ export function askAndCall(questions, callback) {
|
||||
|
||||
callback();
|
||||
}
|
||||
|
||||
export function askAndCallReconnect() {
|
||||
askAndCall([askSaveSettings, askDisconnect], () => {
|
||||
sendAction("reconnect");
|
||||
});
|
||||
}
|
||||
|
||||
export function askAndCallReboot() {
|
||||
askAndCall([askSaveSettings, askReboot], () => {
|
||||
sendAction("reboot");
|
||||
});
|
||||
}
|
||||
|
||||
export function askAndCallAction(event) {
|
||||
askAndCall([(ask) => ask(`Confirm the action: "${event.target.textContent}"`)], () => {
|
||||
sendAction(event.target.name);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from './template.mjs';
|
||||
|
||||
import {
|
||||
showPanel,
|
||||
onPanelTargetClick,
|
||||
showPanelByName,
|
||||
styleInject,
|
||||
styleVisible,
|
||||
@@ -358,7 +358,7 @@ export function init() {
|
||||
variableListeners(listeners());
|
||||
|
||||
document.querySelector(".button-emon-expected")
|
||||
.addEventListener("click", showPanel);
|
||||
.addEventListener("click", onPanelTargetClick);
|
||||
document.querySelector(".button-emon-expected-calculate")
|
||||
.addEventListener("click", emonCalculateRatios);
|
||||
document.querySelector(".button-emon-expected-apply")
|
||||
|
||||
@@ -1195,6 +1195,20 @@ export function pendingChanges() {
|
||||
return Settings.counters.changed > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO https://github.com/microsoft/TypeScript/issues/58969 ? at-import becomes 'unused' for some reason
|
||||
* @typedef {import("./question.mjs").QuestionWrapper} QuestionWrapper
|
||||
*/
|
||||
|
||||
/** @type {QuestionWrapper} */
|
||||
export function askSaveSettings(ask) {
|
||||
if (pendingChanges()) {
|
||||
return ask("There are pending changes to the settings, continue the operation without saving?");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @returns {KeyValueListeners} */
|
||||
function listeners() {
|
||||
return {
|
||||
@@ -1249,7 +1263,7 @@ export function init() {
|
||||
|
||||
document.querySelectorAll(".button-add-settings-group")
|
||||
.forEach((elem) => {
|
||||
elem.addEventListener("click", groupSettingsAdd);
|
||||
elem.addEventListener("click", onGroupSettingsAddClick);
|
||||
});
|
||||
|
||||
// No group handler should be registered after this point, since we depend on the order
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { askAndCall, askSaveSettings } from './question.mjs';
|
||||
import { askAndCall } from './question.mjs';
|
||||
import { askSaveSettings } from './settings.mjs';
|
||||
import { sendAction } from './connection.mjs';
|
||||
|
||||
function checkTempRange(event) {
|
||||
|
||||
Reference in New Issue
Block a user