Files
WLED_MM_Infinity/wled00/data/settings_um.htm
2023-03-28 11:38:47 +02:00

381 lines
15 KiB
HTML

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=500">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
<title>Usermod Settings</title>
<script>
var d = document;
d.max_gpio = 39;
d.um_p = [];
d.rsvd = [];
d.ro_gpio = [];
var umCfg = {};
var pins = [], pinO = [], owner;
var loc = false, locip;
var urows;
// var numM = 0;
function gId(s) { return d.getElementById(s); }
function isO(i) { return (i && typeof i === 'object' && !Array.isArray(i)); }
function H() { window.open("https://github.com/Aircoookie/WLED/wiki/Settings#usermod-settings"); }
function B() { window.open("/settings","_self"); }
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
scE.setAttribute("src", FILE_URL);
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
GetV();
for (let k=0; k<d.rsvd.length; k++) { pins.push(d.rsvd[k]); pinO.push("rsvd"); }
if (d.um_p[0]==-1) d.um_p.shift();
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
function S() {
if (window.location.protocol == "file:") {
loc = true;
locip = localStorage.getItem('locIp');
if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip);
}
}
ldS();
// if (!numM) gId("um").innerHTML = ""; //WLEDMM: Do not display no usermods installed
}
// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
function isF(n) { return n === +n && n !== (n|0); }
function isI(n) { return n === +n && n === (n|0); }
// function check(o,k) {} //WLEDMM not needed as we use dropdowns
function getPins(o) {
if (isO(o)) {
for (const [k,v] of Object.entries(o)) {
if (isO(v)) {
owner = k;
getPins(v);
continue;
}
if (k.replace("[]","").substr(-3)=="pin") {
if (Array.isArray(v)) {
for (var i=0; i<v.length; i++) if (v[i]>=0) { pins.push(v[i]); pinO.push(owner); }
} else {
if (v>=0) { pins.push(v); pinO.push(owner); }
}
} else if (Array.isArray(v)) {
for (var i=0; i<v.length; i++) getPins(v[i]);
}
}
}
}
//WLEDMM util function
function initCap(s) {
if (typeof s !== 'string') return '';
// https://www.freecodecamp.org/news/how-to-capitalize-words-in-javascript/
return s.replace(/[\W_]/g,' ').replace(/(^\w{1})|(\s+\w{1})/g, l=>l.toUpperCase()); // replace - and _ with space, capitalize every 1st letter
}
function addField(k,f,o,a=false) { //key, field, (sub)object, isArray
if (isO(o)) {
urows += '<hr class="sml">';
if (f!=='unknown' && !k.includes(":")) urows += `<p><u>${initCap(f)}</u></p>`; //WLEDMM show group title
for (const [s,v] of Object.entries(o)) {
// possibility to nest objects (only 1 level)
if (f!=='unknown' && !k.includes(":")) addField(k+":"+f,s,v);
else addField(k,s,v);
}
} else if (Array.isArray(o)) {
for (var j=0; j<o.length; j++) {
addField(k,f,o[j],true);
}
} else {
var c, t = typeof o;
switch (t) {
case "boolean":
t = "checkbox"; c = 'value="true"' + (o ? ' checked' : '');
break;
case "number":
c = `value="${o}"`;
if (f.substr(-3)==="pin") { //WLEDMM: this will not be used and should never happen as pins are now dropdowns
c += ` max="50" min="-1" class="s"`; //WLEDMM: hardcoded to 50 as d.max_gpio is not calculated yet here (done in appendGPIOinfo)
t = "int";
} else {
c += ' step="any" class="xxl"';
}
break;
default:
t = "text"; c = `value="${o}" style="width:250px;"`;
break;
}
urows += ` ${initCap(f)} `; //WLEDMM only show field (key is shown in grouping)
// https://stackoverflow.com/questions/11657123/posting-both-checked-and-unchecked-checkboxes
if (t=="checkbox") urows += `<input type="hidden" name="${k}:${f}${a?"[]":""}" value="false">`;
else if (!a) urows += `<input type="hidden" name="${k}:${f}${a?"[]":""}" value="${t}">`;
// WLEDMM make a dropdown for pin variables
if (f.includes("pin")) {
var n = this.name.replace("[]","").substr(-3);
urows += `<select name="${k}:${f}${a?"[]":""}" style="width:200px;max-width:90%;">`; //WLEDMM width capped. to be moved to css one day
for (var j=-1; j<=50; j++) { // all possible pins WLEDMM: hardcoded to 50 as d.max_gpio is not calculated yet here (done in appendGPIOinfo)
let foundPin = -1;
for (var i=0; i<pins.length; i++) { // check if pin is reserved
if (pins[i] == j) foundPin = i;
}
urows += `<option value="${j}"`;
if (j==o) urows += ` selected`; //add selected value
if (foundPin >=0) {// already reserved pin
if (!k.includes(pinO[foundPin])) urows += ` disabled`; // && pinO[foundPin] != "if"
}
if (j==-1) urows += `>undefined`; else urows += `>${j}`; // don't show -1
if (foundPin >=0) {// already reserved pin
urows += ` ${pinO[foundPin]=="if"?"global":pinO[foundPin]}`;
if (!k.includes(pinO[foundPin])) urows += " 🔴"; else urows += " 🟢"; //add pins assigned here || pinO[foundPin] == "if"
}
urows += `</option>`;
// if (j==5) //exclude pin 6 to 11
// j=11
}
urows += `</select>`;
}
else
urows += `<input type="${t==="int"?"number":t}" name="${k}:${f}${a?"[]":""}" ${c}>`; //WLEDMM no need for oninput="check(this,'${k.substr(k.indexOf(":")+1)}')"
urows += `<br>`;
}
}
// https://stackoverflow.com/questions/39729741/javascript-change-input-text-to-select-option
function addDropdown(um,fld) {
let sel = d.createElement('select');
let arr = d.getElementsByName(um+":"+fld);
let inp = arr[1]; // assume 1st field to be hidden (type)
if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName
let v = inp.value;
let n = inp.name;
// copy the existing input element's attributes to the new select element
for (var i = 0; i < inp.attributes.length; ++ i) {
var att = inp.attributes[i];
// type and value don't apply, so skip them
// ** you might also want to skip style, or others -- modify as needed **
if (att.name != 'type' && att.name != 'value' && att.name != 'class' && att.name != 'style') {
sel.setAttribute(att.name, att.value);
}
}
sel.setAttribute("data-val", v);
// finally, replace the old input element with the new select element
inp.parentElement.replaceChild(sel, inp);
return sel;
}
return null;
}
function addOption(sel,txt,val) {
if (sel===null) return; // select object missing
let opt = d.createElement("option");
opt.value = val;
opt.text = txt;
sel.appendChild(opt);
for (let i=0; i<sel.childNodes.length; i++) {
let c = sel.childNodes[i];
if (c.value == sel.dataset.val) sel.selectedIndex = i;
}
}
//WLEDMM: replace Option to set globals
function rOpt(name,el,txt,val) {
let obj = d.getElementsByName(name);
var select = obj;
if (obj[el]) select = obj[el];
for (let i=0; i<select.childNodes.length; i++) {
let c = select.childNodes[i];
if (c.value == val) c.text = txt;
}
}
//WLEDMM: extend Option to set build flag defaults
function xOpt(name,el,txt,val) {
let obj = d.getElementsByName(name);
var select = obj;
if (obj[el]) select = obj[el];
for (let i=0; i<select.childNodes.length; i++) {
let c = select.childNodes[i];
if (c.value == val) c.text += txt;
}
}
//WLEDMM: delete Options to remove options e.g. mclk
function dOpt(name,el,valFrom,valTo) {
let obj = d.getElementsByName(name);
var select = obj;
if (obj[el]) select = obj[el];
for (let i=0; i<select.options.length; i++) {
let c = select.options[i];
if (c.value >= valFrom && c.value <= valTo) {
select.removeChild(c);
i--; //decrease i by one because the index has been adjusted
}
//https://www.javascripttutorial.net/javascript-dom/javascript-add-remove-options/
//https://www.javascripttutorial.net/javascript-dom/javascript-remove-items-from-a-select-conditionally/
}
}
//WLEDMM: analog options only
function aOpt(name,el) {
let obj = d.getElementsByName(name);
var select = obj;
if (obj[el]) select = obj[el];
for (let i=0; i<select.options.length; i++) {
let c = select.options[i];
found = false;
for (let jj=0; jj<d.a_pins.length; jj++) if (d.a_pins[jj] == c.value) found = true; //value -1 or analog pins
if (c.value != -1 && !found) {
select.removeChild(c);
i--; //decrease i by one because the index has been adjusted
}
//https://www.javascripttutorial.net/javascript-dom/javascript-add-remove-options/
//https://www.javascripttutorial.net/javascript-dom/javascript-remove-items-from-a-select-conditionally/
}
}
//WLEDMM disable read only pins
function dRO(name,el) {
let obj = d.getElementsByName(name);
var select = obj;
if (obj[el]) select = obj[el];
// console.log("dRO", name, el, obj, "s", select, d.ro_gpio);
for (let i=0; i<select.options.length; i++) {
let c = select.options[i];
// console.log("dRO option", c, c.value, d.ro_gpio.includes(c.value));
for (let j=0; j<d.ro_gpio.length; j++) if (d.ro_gpio[j] == c.value) c.disabled=true; //if (d.ro_gpio.includes(c.value))
}
}
//WLEDMM read only pins 🟠, reserved pins 🟣 and disabled, and remove pins > max_gpio
function pinPost() {
// console.log('pinPost', d.max_gpio, d.ro_gpio, d.rsvd);
var elements = gId("form_s").elements;
for (var i = 0, select; select = elements[i++];) {
if (select.name.includes("pin") && select.options!=null) { //select all pin select elements
// console.log(element);
for (let i=0; i<select.options.length; i++) {
let c = select.options[i];
// console.log("pinPost option", c, c.value, d.ro_gpio.includes(c.value));
for (let j=0; j<d.ro_gpio.length; j++) if (d.ro_gpio[j] == c.value) c.text += " read only 🟠"; //if (d.ro_gpio.includes(c.value)) not working ???
for (let j=0; j<d.rsvd.length; j++) if (d.rsvd[j] == c.value) {c.text += " reserved 🟣"; c.disabled=true;} //now always disabled as post is done last if (d.rsvd.includes(c.value))
//remove pins > max_gpio
if (c.value > d.max_gpio) {
select.removeChild(c);
i--; //decrease i by one because the index has been adjusted
}
//https://www.javascripttutorial.net/javascript-dom/javascript-add-remove-options/
//https://www.javascripttutorial.net/javascript-dom/javascript-remove-items-from-a-select-conditionally/
if (c.text.length <= 4) c.text += " 🟢"; //2 digit number space and ⍼/⎌. If no reserved/read only/other um, then pin can be freely used (green)
for (let jj=0; jj<d.dt_pins.length; jj++) if (d.dt_pins[jj] == c.value) c.text += ((jj<9)?` D${jj}`:((jj==9)?` RX`:` TX`)); //WLEDMM: Add D0-D8, RX/TX to name
for (let jj=0; jj<d.a_pins.length; jj++) if (d.a_pins[jj] == c.value) c.text += ` A${jj}`; //WLEDMM: Add A0-A10
}
}
}
}
// https://stackoverflow.com/questions/26440494/insert-text-after-this-input-element-with-javascript
function addInfo(name,el,txt, txt2="") {
let obj = d.getElementsByName(name);
if (!obj.length) return;
if (typeof el === "string" && obj[0]) obj[0].placeholder = el;
else if (obj[el]) {
if (txt!="") obj[el].insertAdjacentHTML('afterend', '&nbsp;'+txt);
if (txt2!="") obj[el].insertAdjacentHTML('beforebegin', txt2 + '&nbsp;'); //add pre texts
}
}
// add Help Button
function addHB(um)
{
addInfo(um + ':help',0,'<button onclick=\"location.href=&quot;https://mm.kno.wled.ge/usermods/' + um + '&quot;\" type=\"button\">?</button>')
}
// load settings and insert values into DOM
function ldS() {
var url = (loc?`http://${locip}`:'') + '/cfg.json';
fetch(url, {
method: 'get'
})
.then(res => {
if (!res.ok) gId('lserr').style.display = "inline";
return res.json();
})
.then(json => {
umCfg = json.um;
getPins(json);
urows="";
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const userMod = urlParams.get('um');
if (userMod == null) {
urows+="<hr><h2>Usermods (Pin Manager)</h2>";
urows+="<button onclick=\"location.href=&quot;https://mm.kno.wled.ge/usermods/pinmanager&quot;\" type=\"button\">?</button>";
urows+="<hr class=\"sml\">";
urows+="<i style=\"color:orange\">(change requires reboot!)</i>";
urows+="<hr class=\"sml\">";
urows+="<p><u>Global I2C GPIOs (HW)</u></p>";
addField("if:SDA", "pin", -1, false);
addField("if:SCL", "pin", -1, false);
urows+="<hr class=\"sml\">";
urows+="<p><u>Global SPI GPIOs (HW)</u></p>";
addField("if:MOSI", "pin", -1, false);
addField("if:MISO", "pin", -1, false);
addField("if:SCLK", "pin", -1, false);
}
if (isO(umCfg)) {
//WLEDMM: read url parameter. e.g. um=AudioReactive and if set only add the usermod with the same name
for (const [k,o] of Object.entries(umCfg)) {
if (userMod == k) {
urows += `<hr><h3>${k}</h3><div name="${k}:help"></div>`;
addField(k,'unknown',o);
}
}
if (userMod != null && urows==="") urows = "Usermods configuration not found.<br>Press <i>Save</i> to initialize defaults.";
if (userMod == null) {
urows += `<hr> `;
urows += `<div style="margin: auto;">`;
urows += `<table style="margin-left:auto; margin-right:auto;">`;
urows += `<tr><th>Usermod</th><th>Enabled</th></tr>`
for (const [k,o] of Object.entries(umCfg)) {
urows += `<tr><td>${k}</td><td>${o.enabled}</td></tr>`;
}
urows += `</table>`;
urows += `</div>`;
}
}
gId("um").innerHTML = urows;
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=8';
if (userMod != null) url+= '&um=' + userMod; //WLEDMM add userMod as parameter
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
})
.catch((error)=>{
gId('lserr').style.display = "inline";
console.log(error);
});
}
function svS(e) {
e.preventDefault();
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
}
</script>
<style>@import url("style.css");</style>
</head>
<body onload="S()">
<form id="form_s" name="Sf" method="post" onsubmit="svS(event)">
<div class="toprow">
<div class="helpB"><button type="button" onclick="H()">?</button></div>
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><br>
<span id="lssuc" style="color:green; display:none">&#10004; Configuration saved!</span>
<span id="lserr" style="color:red; display:none">&#9888; Could not load configuration.</span>
</div>
<!-- WLEDMM: no gpios here as it is generated -->
<div id="um">Loading settings...</div>
<div name="errorMessage"></div>
<hr><button type="button" onclick="B()">Back</button><button type="submit">Save</button>
</form>
</body>
</html>