396 lines
16 KiB
HTML
396 lines
16 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<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 = [];
|
||
d.h_pins = [];
|
||
d.x_pins = [];
|
||
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://mm.kno.wled.ge/usermods/AutoPlaylist/"); }
|
||
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>`;
|
||
}
|
||
}
|
||
function adF(k,f,o,a=false) { //shortcut for addField(key, field, (sub)object, isArray)
|
||
return addField(k,f,o,a);
|
||
}
|
||
// 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 adD(um,fld) { // shortcut for addDropdown(um,fld)
|
||
return addDropdown(um,fld);
|
||
}
|
||
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;
|
||
}
|
||
}
|
||
function adO(sel,txt,val) { // shortcut for addOption(sel,txt,val)
|
||
return addOption(sel,txt,val);
|
||
}
|
||
//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))
|
||
for (let j=0; j<d.h_pins.length; j++) if (d.h_pins[j] == c.value && c.text.length <= 4) {c.text += " HUB75 🔴"; c.disabled=true;} // HUB75 pins
|
||
for (let j=0; j<d.x_pins.length; j++) if (d.x_pins[j] == c.value && c.text.length <= 4) {c.text += " DMX 🔴"; c.disabled=true;} // DMX pins
|
||
//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', ' '+txt);
|
||
if (txt2!="") obj[el].insertAdjacentHTML('beforebegin', txt2 + ' '); //add pre texts
|
||
}
|
||
}
|
||
function adI(name,el,txt, txt2="") { // shortcut for addInfo(name,el,txt, txt2="")
|
||
return addInfo(name,el,txt, txt2);
|
||
}
|
||
// add Help Button
|
||
function addHB(um)
|
||
{
|
||
addInfo(um + ':help',0,'<button onclick=\"location.href="https://mm.kno.wled.ge/usermods/' + um + '"\" 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="https://mm.kno.wled.ge/usermods/pinmanager"\" 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">✔ Configuration saved!</span>
|
||
<span id="lserr" style="color:red; display:none">⚠ 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> |