From 00fab2e86d758d11c374e444b637f6659a107eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 13 Oct 2025 08:19:48 +0200 Subject: [PATCH 1/5] Drag and drop LED output reordering --- wled00/data/settings_leds.htm | 60 ++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 8a3330e473..7a1060b9c4 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -1,4 +1,4 @@ - + @@ -131,7 +131,7 @@ if (!en) { // limiter disabled d.Sf.PPL.checked = false; -// d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((e)=>{e.selectedIndex = 0;}); // select default LED mA +// d.Sf.querySelectorAll("#mLC select[name^=LL]").forEach((e)=>{e.selectedIndex = 0;}); // select default LED mA // d.Sf.querySelectorAll("#mLC input[name^=LA]").forEach((e)=>{e.min = 0; e.value = 0;}); // set min & value to 0 } UI(); @@ -160,9 +160,10 @@ if (ppl) d.Sf.MA.value = sumMA; // populate UI ABL value if PPL used } // enable and update LED Amps - function enLA(s,n) + function enLA(s) { const abl = d.Sf.ABL.checked; + const n = s.name.substring(2); // bus number (0-Z) const t = parseInt(d.Sf["LT"+n].value); // LED type SELECT gId('LAdis'+n).style.display = s.selectedIndex==5 ? "inline" : "none"; // show/hide custom mA field if (s.value!=="0") d.Sf["LA"+n].value = s.value; // set value from select object @@ -177,9 +178,9 @@ }); d.Sf.ABL.checked = en; // select appropriate LED current - d.Sf.querySelectorAll("#mLC select[name^=LAsel]").forEach((sel,x)=>{ + d.Sf.querySelectorAll("#mLC select[name^=LL]").forEach((sel)=>{ sel.value = 0; // set custom - var n = chrID(x); + var n = sel.name.substring(2); // bus number (0-Z) if (en) switch (parseInt(d.Sf["LA"+n].value)) { case 0: break; // disable ABL @@ -190,7 +191,7 @@ case 255: sel.value = 255; break; } else sel.value = 0; - enLA(sel,n); // configure individual limiter + enLA(sel); // configure individual limiter }); enABL(); gId('m1').innerHTML = maxM; @@ -464,12 +465,12 @@ if (n==1) { // npm run build has trouble minimizing spaces inside string - var cn = `
+ var cn = `

-${i+1}: +${i+1}:
-mA/LED: @@ -521,7 +522,7 @@ } } }); - enLA(d.Sf["LAsel"+s],s); // update LED mA + enLA(gN("LL"+s)); // update LED mA // disable inappropriate LED types let sel = d.getElementsByName("LT"+s)[0]; // 32 & S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel @@ -617,6 +618,43 @@ c += ` ✕
`; gId("btns").innerHTML = c; } + function hDS(e) { + e.dataTransfer.setData('text', e.currentTarget.id); + } + function hDO(e) { + e.preventDefault(); + } + function hDrop(e) { + e.preventDefault(); + let t = e.target; + if (t.id === "mLC") t = t.children[0]; // dropped on a container, not on an element + else while (t && !t.classList.contains("iST")) t = t.parentNode; // find target element + if (!t || t.id === "") return false; // not dropping on a valid target + const id = e.dataTransfer.getData("text"); + t.parentNode.insertBefore(gId(id), t); + recalcIds(); + UI(); + } + function recalcIds() { + gId("mLC").querySelectorAll(".iST").forEach((e,i)=>{ + let sOld = e.id.substring(1); + let sNew = chrID(i); + // update all element IDs and names + e.id = "l"+sNew; + e.querySelector("#n"+sOld).innerText = (i+1); + // names: LT,LL,LA,MA,CO,WO,SP,LS,LC,L0,L1,L2,L3,L4,HS,CV,SL,RF,AW + ["LT","LL","LA","MA","CO","WO","SP","LS","LC","L0","L1","L2","L3","L4","HS","CV","SL","RF","AW"].forEach((n)=>{ + let el = e.querySelector("[name^="+n+"]"); + if (el) el.name = n + sNew; + }); + // IDs: l,n,abl,LAdis,PSU,co,ls,dig?w,dig?l,psd,dig?c,p0d,p1d,p2d,p3d,p4d,net?h,dig?r,dig?s,dig?f,dig?a + ["l","n","abl","LAdis","PSU","co","ls","dig?w","dig?l","psd","dig?c","p0d","p1d","p2d","p3d","p4d","net?h","dig?r","dig?s","dig?f","dig?a"].forEach((n)=>{ + if (n.indexOf("?") < 0) n += "?"; + let el = e.querySelector("#"+n.replace("?", sOld)); + if (el) el.id = n.replace("?", sNew); + }); + }); + } function tglSi(cs) { customStarts = cs; if (!customStarts) startsDirty = []; //set all starts to clean @@ -847,7 +885,7 @@

LED & Hardware setup

Hardware setup

-
LED outputs:
+
LED outputs:


From a18b133942bea56fd88e732fec33c14e3b0372be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 20 Oct 2025 20:33:23 +0200 Subject: [PATCH 2/5] Bugfix Reordering did not enable editing last bus' type. --- wled00/data/settings_leds.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 7a1060b9c4..3b7ded5275 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -271,7 +271,7 @@ let dC = 0; // count of digital buses (for parallel I2S) let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]"); LTs.forEach((s,i)=>{ - if (i < LTs.length-1) s.disabled = true; // prevent changing type (as we can't update options) + s.disabled = (i < LTs.length-1); // prevent changing type (as we can't update options) // is the field a LED type? var n = s.name.substring(2,3); // bus number (0-Z) var t = parseInt(s.value); From a03f7584019461cce3ac81c0d45d05aa40c0fc5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Sun, 2 Nov 2025 21:20:40 +0100 Subject: [PATCH 3/5] Add ability to edit LED type on any output - some string optimisations - imports from/replaces #5014 --- wled00/data/settings_leds.htm | 153 +++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 69 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 3b7ded5275..79c9179594 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -1,4 +1,4 @@ - + @@ -13,8 +13,9 @@ function gT(t) { for (let type of d.ledTypes) if (t == type.i) return type; } // getType from available ledTypes function isPWM(t) { return gT(t).t.charAt(0) === "A"; } // is PWM type function isAna(t) { return gT(t).t === "" || isPWM(t); } // is analog type - function isDig(t) { return gT(t).t === "D" || isD2P(t); } // is digital type + function isD1P(t) { return gT(t).t === "D"; } // is digital 1 pin type function isD2P(t) { return gT(t).t === "2P"; } // is digital 2 pin type + function isDig(t) { return isD1P(t) || isD2P(t); } // is digital type function isNet(t) { return gT(t).t === "N"; } // is network type function isVir(t) { return gT(t).t === "V" || isNet(t); } // is virtual type function isHub75(t){ return gT(t).t === "H"; } // is HUB75 type @@ -71,7 +72,7 @@ return; } // ignore IP address - if (isNet(t)) return; + if (isNet(t)) return; //check for pin conflicts if (LC.value!="" && LC.value!="-1") { let p = d.rsvd.concat(d.um_p); // used pin array @@ -101,10 +102,10 @@ nList[j].focus(); ok = false; return; - } } } } + } }); return ok; } @@ -215,8 +216,8 @@ mul = 2; // ESP32 RMT uses double buffer } else if ((is32() || isS2() || isS3()) && toNum(n) > (parallelI2S ? 7 : 0)) { mul = 2; // ESP32 RMT uses double buffer - } else if ((parallelI2S && toNum(n) < 8) || (n == 0 && is32())) { // I2S uses extra DMA buffer - dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: ony the bus with largst LED count should be used) + } else if ((parallelI2S && toNum(n) < 8) || (toNum(n) == 0 && is32())) { // I2S uses extra DMA buffer + dbl = len * ch * 3; // DMA buffer for parallel I2S (TODO: only the bus with largst LED count should be used) } } return len * ch * mul + dbl + pbfr; @@ -229,7 +230,7 @@ let sLC = 0, sPC = 0, sDI = 0, maxLC = 0; const abl = d.Sf.ABL.checked; let setPinConfig = (n,t) => { - let p0d = "GPIO:"; + let p0d = "GPIO: "; let p1d = ""; let off = "Off Refresh"; switch (gT(t).t.charAt(0)) { @@ -240,14 +241,14 @@ p0d = "Data "+p0d; break; case 'A': // PWM analog - if (numPins(t) > 1) p0d = "GPIOs:"; + if (numPins(t) > 1) p0d = "GPIOs: "; off = "Dithering"; break; case 'N': // network - p0d = "IP address:"; + p0d = "IP address: "; break; case 'V': // virtual/non-GPIO based - p0d = "Config:" + p0d = "Config: " break; case 'H': // HUB75 p0d = "Panel size (width x height), Panel count:" @@ -268,11 +269,10 @@ } // enable/disable LED fields + updateTypeDropdowns(change); let dC = 0; // count of digital buses (for parallel I2S) let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]"); LTs.forEach((s,i)=>{ - s.disabled = (i < LTs.length-1); // prevent changing type (as we can't update options) - // is the field a LED type? var n = s.name.substring(2,3); // bus number (0-Z) var t = parseInt(s.value); memu += getMem(t, n); // calc memory @@ -321,15 +321,16 @@ } // do we have a led count field if (nm=="LC") { + LC.max = isAna(t) ? 1 : (isDig(t) ? maxPB : 16384); // set max value let c = parseInt(LC.value,10); //get LED count if (!customStarts || !startsDirty[toNum(n)]) gId("ls"+n).value = sLC; //update start value gId("ls"+n).disabled = !customStarts; //enable/disable field editing if (c) { let s = parseInt(gId("ls"+n).value); //start value if (s+c > sLC) sLC = s+c; //update total count - if (c > maxLC) maxLC = c; //max per output if (!isVir(t)) sPC += c; //virtual out busses do not count towards physical LEDs if (isDig(t)) { + if (c > maxLC) maxLC = c; //max per output sDI += c; // summarize digital LED count let maPL = parseInt(d.Sf["LA"+n].value); if (maPL == 255) maPL = 12; @@ -409,6 +410,7 @@ gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)"; // memory usage and warnings + memu += sLC * (4 + (gN("MS").checked ? 0 : 4)); // pixel buffer (4 bytes per pixel) + at least 1 segment buffer gId('m0').innerHTML = memu; bquot = memu / maxM * 100; gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%, #444 ${bquot}% 100%)`; @@ -439,8 +441,8 @@ function lastEnd(i) { if (i-- < 1) return 0; var s = chrID(i); - v = parseInt(d.getElementsByName("LS"+s)[0].value) + parseInt(d.getElementsByName("LC"+s)[0].value); - var t = parseInt(d.getElementsByName("LT"+s)[0].value); + v = parseInt(gN("LS"+s).value) + parseInt(gN("LC"+s).value); + var t = parseInt(gN("LT"+s).value); if (isPWM(t)) v = 1; //PWM busses return isNaN(v) ? 0 : v; } @@ -448,17 +450,7 @@ { var o = gEBCN("iST"); var i = o.length; - let disable = (sel,opt) => { sel.querySelectorAll(opt).forEach((o)=>{o.disabled=true;}); } - var f = gId("mLC"); - let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0; - f.querySelectorAll("select[name^=LT]").forEach((s)=>{ - let t = s.value; - if (isDig(t) && !isD2P(t)) digitalB++; - if (isD2P(t)) twopinB++; - if (isPWM(t)) analogB += numPins(t); // each GPIO is assigned to a channel - if (isVir(t)) virtB++; - }); if ((n==1 && i>=36) || (n==-1 && i==0)) return; // used to be i>=maxB+maxV when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") var s = chrID(i); @@ -467,7 +459,7 @@ // npm run build has trouble minimizing spaces inside string var cn = `

-${i+1}: +${++i}:
mA/LED:
-
PSU: mA
+
PSU: mA
Color Order:
-Start:   +Start:  
Length:

GPIO: @@ -523,22 +515,15 @@ } }); enLA(gN("LL"+s)); // update LED mA - // disable inappropriate LED types - let sel = d.getElementsByName("LT"+s)[0]; - // 32 & S2 supports mono I2S as well as parallel so we need to take that into account; S3 only supports parallel - let maxDB = maxD - (is32() || isS2() || isS3() ? (!d.Sf["PR"].checked)*8 - (!isS3()) : 0); // adjust max digital buses if parallel I2S is not used - if (digitalB >= maxDB) disable(sel,'option[data-type="D"]'); // NOTE: see isDig() - if (twopinB >= 2) disable(sel,'option[data-type="2P"]'); // NOTE: see isD2P() (we will only allow 2 2pin buses) - disable(sel,`option[data-type^="${'A'.repeat(maxA-analogB+1)}"]`); // NOTE: see isPWM() - sel.selectedIndex = sel.querySelector('option:not(:disabled)').index; + let sel = gN("LT"+s); + sel.selectedIndex = sel.querySelector('option:not([data-type])').index; // select On/Off by default } if (n==-1) { - o[--i].remove();--i; - o[i].querySelector("[name^=LT]").disabled = false; + o[--i].remove(); } - gId("+").style.display = (i<35) ? "inline":"none"; // was maxB+maxV-1 when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") - gId("-").style.display = (i>0) ? "inline":"none"; + gId("+").style.display = (i<36) ? "inline":"none"; // was maxB+maxV-1 when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") + gId("-").style.display = (i>1) ? "inline":"none"; if (!init) { UI(); @@ -703,23 +688,23 @@ let l = c.hw.led; l.ins.forEach((v,i,a)=>{ addLEDs(1); - for (var j=0; j>4) & 0x0F; - d.getElementsByName("SP"+i)[0].value = v.freq; - d.getElementsByName("LA"+i)[0].value = v.ledma; - d.getElementsByName("MA"+i)[0].value = v.maxpwr; + for (var j=0; j>4) & 0x0F; + gN("SP"+i).value = v.freq; + gN("LA"+i).value = v.ledma; + gN("MA"+i).value = v.maxpwr; }); - d.getElementsByName("PR")[0].checked = l.prl | 0; - d.getElementsByName("MA")[0].value = l.maxpwr; - d.getElementsByName("ABL")[0].checked = l.maxpwr > 0; + gN("PR").checked = l.prl | 0; + gN("MA").value = l.maxpwr; + gN("ABL").checked = l.maxpwr > 0; } if(c.hw.com) { resetCOM(); @@ -733,22 +718,22 @@ b.ins.forEach((v,i,a)=>{ addBtn(i,v.pin[0],v.type); }); - d.getElementsByName("TT")[0].value = b.tt; + gN("TT").value = b.tt; } let ir = c.hw.ir; if (ir) { - d.getElementsByName("IR")[0].value = ir.pin; - d.getElementsByName("IT")[0].value = ir.type; + gN("IR").value = ir.pin; + gN("IT").value = ir.type; } let rl = c.hw.relay; if (rl) { - d.getElementsByName("RL")[0].value = rl.pin; - d.getElementsByName("RM")[0].checked = rl.rev; - d.getElementsByName("RO")[0].checked = rl.odrain; + gN("RL").value = rl.pin; + gN("RM").checked = rl.rev; + gN("RO").checked = rl.odrain; } let li = c.light; if (li) { - d.getElementsByName("MS")[0].checked = li.aseg; + gN("MS").checked = li.aseg; } UI(); } @@ -817,7 +802,7 @@ function addDropdown(field) { let sel = cE('select'); sel.classList.add("pin"); - let inp = d.getElementsByName(field)[0]; + let inp = gN(field); if (inp && inp.tagName === "INPUT" && (inp.type === "text" || inp.type === "number")) { // may also use nodeName let v = inp.value; let n = inp.name; @@ -850,6 +835,36 @@ } return opt; } + // dynamically enforce bus type availability based on current usage + function updateTypeDropdowns(change=false) { + let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]"); + let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0; + // count currently used buses (including last added bus) + LTs.forEach((sel,i) => { + let t = parseInt(sel.value); + if (isD1P(t)) digitalB++; + if (isPWM(t)) analogB += numPins(t); + if (isD2P(t)) twopinB++; + if (isVir(t)) virtB++; + }); + // now enable/disable type options according to limits in dropdowns + LTs.forEach((sel,i) => { + const last = i == LTs.length-1; + const curType = parseInt(sel.value); + const disable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = true); + const enable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = false); + enable('option'); // reset all first + // max digital count + let maxDB = maxD - ((is32() || isS2() || isS3()) ? (!d.Sf["PR"].checked) * 8 - (!isS3()) : 0); + // disallow adding more of a type that has reached its limit + if (digitalB >= maxDB && !isD1P(curType)) disable('option[data-type="D"]'); + if (twopinB >= 2 && !isD2P(curType)) disable('option[data-type="2P"]'); + // determine available analog pins + disable(`option[data-type^="${'A'.repeat(maxA - analogB + (isPWM(curType) ? numPins(curType) : 0) + 1)}"]`); + // if last added bus has an invalid type, change it to first available + if (last && sel.selectedOptions[0].disabled) sel.selectedIndex = sel.querySelector('option:not(:disabled)').index; + }); + } @@ -871,7 +886,7 @@

LED & Hardware setup

Keep at <1A if powering LEDs directly from the ESP 5V pin!
If using multiple outputs it is recommended to use per-output limiter.
Analog (PWM) and virtual LEDs cannot use automatic brightness limiter.
-
Maximum PSU Current: mA
+
Maximum PSU Current: mA
Use per-output limiter:

Use parallel I2S:
- Make a segment for each output:
+ Make a segment for each output:
Custom bus start indices:

@@ -905,7 +920,7 @@

Hardware setup


-
+

@@ -934,9 +949,9 @@

Defaults

Turn LEDs on after power up/reset:
Default brightness: (1-255)

Apply preset at boot (0 uses values from above)

- Use Gamma correction for color: (strongly recommended)
+ Use Gamma correction for color: (recommended)
Use Gamma correction for brightness: (not recommended)
- Use Gamma value:

+ Use Gamma value:

Brightness factor: %

Transitions

Default transition time: ms
From 7f8977fa3d5b6bc9ebd20920366c495049bfc8d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 3 Nov 2025 14:00:19 +0100 Subject: [PATCH 4/5] Remove memory calculation, add comment --- wled00/data/settings_leds.htm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index 79c9179594..a74171caa9 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -119,7 +119,6 @@ if (bquot > 100) msg += "\n\rToo many LEDs for me to handle properly!"; if (maxM < 10000) msg += "\n\rConsider using an ESP32."; alert(msg);} if (!d.Sf.ABL.checked || d.Sf.PPL.checked) d.Sf.MA.value = 0; // submit 0 as ABL (PPL will handle it) if (d.Sf.checkValidity()) { - d.Sf.querySelectorAll("#mLC select[name^=LT]").forEach((s)=>{s.disabled=false;}); // just in case d.Sf.submit(); //https://stackoverflow.com/q/37323914 } } @@ -300,7 +299,7 @@ gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°) //gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266 - if (!isNet(t) || is8266()) d.Sf["HS"+n].value = ""; // cleart host field if not network type or ESP8266 + if (!isNet(t) || is8266()) d.Sf["HS"+n].value = ""; // cleart host field if not network type or ESP8266 }); // display global white channel overrides gId("wc").style.display = (gRGBW) ? 'inline':'none'; @@ -410,7 +409,6 @@ gId("pc").textContent = (sLC == sPC) ? "":"(" + sPC + " physical)"; // memory usage and warnings - memu += sLC * (4 + (gN("MS").checked ? 0 : 4)); // pixel buffer (4 bytes per pixel) + at least 1 segment buffer gId('m0').innerHTML = memu; bquot = memu / maxM * 100; gId('dbar').style.background = `linear-gradient(90deg, ${bquot > 60 ? (bquot > 90 ? "red":"orange"):"#ccc"} 0 ${bquot}%, #444 ${bquot}% 100%)`; @@ -525,6 +523,7 @@ gId("+").style.display = (i<36) ? "inline":"none"; // was maxB+maxV-1 when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") gId("-").style.display = (i>1) ? "inline":"none"; + // if called from + or - button, update UI if (!init) { UI(); } @@ -836,7 +835,7 @@ return opt; } // dynamically enforce bus type availability based on current usage - function updateTypeDropdowns(change=false) { + function updateTypeDropdowns(change=false) { // change=true if called from onchange event (reserved for future) let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]"); let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0; // count currently used buses (including last added bus) From df79845dd9e865941946b5e202814a3d18562984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bla=C5=BE=20Kristan?= Date: Mon, 3 Nov 2025 19:53:24 +0100 Subject: [PATCH 5/5] Tuning --- wled00/data/settings_leds.htm | 193 +++++++++++++++++----------------- 1 file changed, 94 insertions(+), 99 deletions(-) diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm index a74171caa9..ad893359b6 100644 --- a/wled00/data/settings_leds.htm +++ b/wled00/data/settings_leds.htm @@ -227,45 +227,6 @@ let gRGBW = false, memu = 0; let busMA = 0; let sLC = 0, sPC = 0, sDI = 0, maxLC = 0; - const abl = d.Sf.ABL.checked; - let setPinConfig = (n,t) => { - let p0d = "GPIO: "; - let p1d = ""; - let off = "Off Refresh"; - switch (gT(t).t.charAt(0)) { - case '2': // 2 pin digital - p1d = "Clock "+p0d; - // fallthrough - case 'D': // digital - p0d = "Data "+p0d; - break; - case 'A': // PWM analog - if (numPins(t) > 1) p0d = "GPIOs: "; - off = "Dithering"; - break; - case 'N': // network - p0d = "IP address: "; - break; - case 'V': // virtual/non-GPIO based - p0d = "Config: " - break; - case 'H': // HUB75 - p0d = "Panel size (width x height), Panel count:" - break; - } - gId("p0d"+n).innerText = p0d; - gId("p1d"+n).innerText = p1d; - gId("off"+n).innerText = off; - // secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off) - let pins = Math.max(gT(t).t.length,1) + 3*isNet(t) + 2*isHub75(t); // fixes network pins to 4 - for (let p=1; p<5; p++) { - var LK = d.Sf["L"+p+n]; - if (!LK) continue; - LK.style.display = (p < pins) ? "inline" : "none"; - LK.required = (p < pins); - if (p >= pins) LK.value=""; - } - } // enable/disable LED fields updateTypeDropdowns(change); @@ -274,33 +235,14 @@ LTs.forEach((s,i)=>{ var n = s.name.substring(2,3); // bus number (0-Z) var t = parseInt(s.value); - memu += getMem(t, n); // calc memory - dC += (isDig(t) && !isD2P(t)); - setPinConfig(n,t); - gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings - if (change) { // did we change LED type? - gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state (mandatory for TM1814) - if (isAna(t)) d.Sf["LC"+n].value = 1; // for sanity change analog count just to 1 LED - d.Sf["LA"+n].min = (!isDig(t) || !abl) ? 0 : 1; // set minimum value for LED mA - d.Sf["MA"+n].min = (!isDig(t)) ? 0 : 250; // set minimum value for PSU mA - } - gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory - gRGBW |= hasW(t); // RGBW checkbox - gId("co"+n).style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide color order for PWM - gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown - gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping - if (!(isDig(t) && hasW(t))) d.Sf["WO"+n].value = 0; // reset swapping - gId("dig"+n+"c").style.display = (isAna(t) || isHub75(t)) ? "none":"inline"; // hide count for analog - gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual - gId("dig"+n+"s").style.display = (isVir(t) || isAna(t) || isHub75(t)) ? "none":"inline"; // hide skip 1st for virtual & analog - gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32) - gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white - gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off) - gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°) - //gId("psd"+n).innerHTML = isAna(t) ? "Index:":"Start:"; // change analog start description - gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266 - if (!isNet(t) || is8266()) d.Sf["HS"+n].value = ""; // cleart host field if not network type or ESP8266 + memu += getMem(t, n); // calc memory + dC += isD1P(t); // count digital buses + gRGBW |= hasW(t); // RGBW checkbox }); + // enable/disable add/remove buttons + gId("+").style.display = (LTs.length<36) ? "inline":"none"; // now: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".length + gId("-").style.display = (LTs.length>1) ? "inline":"none"; + // display global white channel overrides gId("wc").style.display = (gRGBW) ? 'inline':'none'; if (!gRGBW) { @@ -499,34 +441,29 @@
`; f.insertAdjacentHTML("beforeend", cn); // fill led types (credit @netmindz) - f.querySelectorAll("select[name^=LT]").forEach((sel,n)=>{ - if (sel.length == 0) { // ignore already updated - for (let type of d.ledTypes) { - let opt = cE("option"); - opt.value = type.i; - opt.text = type.n; - if (type.t != undefined && type.t != "") { - opt.setAttribute('data-type', type.t); - } - sel.appendChild(opt); - } + let sel = f.lastElementChild.querySelector("select[name^=LT]"); // get recently added LED type select + for (let type of d.ledTypes) { + let opt = cE("option"); + opt.value = type.i; + opt.text = type.n; + if (type.t != undefined && type.t != "") { + opt.setAttribute('data-type', type.t); } - }); + sel.appendChild(opt); + } enLA(gN("LL"+s)); // update LED mA - let sel = gN("LT"+s); - sel.selectedIndex = sel.querySelector('option:not([data-type])').index; // select On/Off by default + if (!init) { + // we used add button + sel.selectedIndex = sel.querySelector('option:not([data-type])').index; // select On/Off by default (prevent selecting first type which may be invalid) + updateTypeDropdowns(true, sel); + sel.selectedIndex = sel.querySelector('option:not(:disabled)').index; // select 1st valid + } } if (n==-1) { o[--i].remove(); } - - gId("+").style.display = (i<36) ? "inline":"none"; // was maxB+maxV-1 when virtual buses were limited (now :"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") - gId("-").style.display = (i>1) ? "inline":"none"; - // if called from + or - button, update UI - if (!init) { - UI(); - } + if (!init) UI(); } function addCOM(start=0,len=1,co=0) { @@ -835,33 +772,91 @@ return opt; } // dynamically enforce bus type availability based on current usage - function updateTypeDropdowns(change=false) { // change=true if called from onchange event (reserved for future) + function updateTypeDropdowns(change=false, sel=undefined) { // change=true if called after a type change + const abl = d.Sf.ABL.checked; let LTs = d.Sf.querySelectorAll("#mLC select[name^=LT]"); let digitalB = 0, analogB = 0, twopinB = 0, virtB = 0; + let setPinConfig = (n,t) => { + let p0d = "GPIO: "; + let p1d = ""; + let off = "Off Refresh"; + switch (gT(t).t.charAt(0)) { + case '2': // 2 pin digital + p1d = "Clock "+p0d; + // fallthrough + case 'D': // digital + p0d = "Data "+p0d; + break; + case 'A': // PWM analog + if (numPins(t) > 1) p0d = "GPIOs: "; + off = "Dithering"; + break; + case 'N': // network + p0d = "IP address: "; + break; + case 'V': // virtual/non-GPIO based + p0d = "Config: " + break; + } + gId("p0d"+n).innerText = p0d; + gId("p1d"+n).innerText = p1d; + gId("off"+n).innerText = off; + // secondary pins show/hide (type string length is equivalent to number of pins used; except for network and on/off) + let pins = Math.max(gT(t).t.length,1) + 3*isNet(t); // fixes network pins to 4 + for (let p=1; p<5; p++) { + var LK = gN("L"+p+n); + if (!LK) continue; + LK.style.display = (p < pins) ? "inline" : "none"; + LK.required = (p < pins); + if (p >= pins) LK.value=""; + } + } // count currently used buses (including last added bus) - LTs.forEach((sel,i) => { - let t = parseInt(sel.value); + LTs.forEach((s,i) => { + let t = parseInt(s.value); if (isD1P(t)) digitalB++; if (isPWM(t)) analogB += numPins(t); if (isD2P(t)) twopinB++; if (isVir(t)) virtB++; }); // now enable/disable type options according to limits in dropdowns - LTs.forEach((sel,i) => { - const last = i == LTs.length-1; - const curType = parseInt(sel.value); - const disable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = true); - const enable = (q) => sel.querySelectorAll(q).forEach(o => o.disabled = false); + if (sel) LTs = [sel]; // only update the changed one + LTs.forEach((s,i) => { + const t = parseInt(s.value); + const disable = (q) => s.querySelectorAll(q).forEach(o => o.disabled = true); + const enable = (q) => s.querySelectorAll(q).forEach(o => o.disabled = false); enable('option'); // reset all first // max digital count let maxDB = maxD - ((is32() || isS2() || isS3()) ? (!d.Sf["PR"].checked) * 8 - (!isS3()) : 0); // disallow adding more of a type that has reached its limit - if (digitalB >= maxDB && !isD1P(curType)) disable('option[data-type="D"]'); - if (twopinB >= 2 && !isD2P(curType)) disable('option[data-type="2P"]'); + if (digitalB >= maxDB && !isD1P(t)) disable('option[data-type="D"]'); + if (twopinB >= 2 && !isD2P(t)) disable('option[data-type="2P"]'); // determine available analog pins - disable(`option[data-type^="${'A'.repeat(maxA - analogB + (isPWM(curType) ? numPins(curType) : 0) + 1)}"]`); - // if last added bus has an invalid type, change it to first available - if (last && sel.selectedOptions[0].disabled) sel.selectedIndex = sel.querySelector('option:not(:disabled)').index; + disable(`option[data-type^="${'A'.repeat(maxA - analogB + (isPWM(t) ? numPins(t) : 0) + 1)}"]`); + // update UI elements + let n = s.name.substring(2,3); // bus number (0-Z) + setPinConfig(n,t); + gId("abl"+n).style.display = (!abl || !isDig(t)) ? "none" : "inline"; // show/hide individual ABL settings + if (change) { // did we change LED type? + gId("rf"+n).checked = (gId("rf"+n).checked || t == 31); // LEDs require data in off state (mandatory for TM1814) + if (isAna(t)) gN("LC"+n).value = 1; // for sanity change analog count just to 1 LED + gN("LA"+n).min = (!isDig(t) || !abl) ? 0 : 1; // set minimum value for LED mA + gN("MA"+n).min = (!isDig(t)) ? 0 : 250; // set minimum value for PSU mA + } + gId("rf"+n).onclick = mustR(t) ? (()=>{return false}) : (()=>{}); // prevent change change of "Refresh" checkmark when mandatory + gId("co"+n).style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide color order for PWM + gId("dig"+n+"w").style.display = (isDig(t) && hasW(t)) ? "inline":"none"; // show swap channels dropdown + gId("dig"+n+"w").querySelector("[data-opt=CCT]").disabled = !hasCCT(t); // disable WW/CW swapping + if (!(isDig(t) && hasW(t))) gN("WO"+n).value = 0; // reset swapping + gId("dig"+n+"c").style.display = (isAna(t)) ? "none":"inline"; // hide count for analog + gId("dig"+n+"r").style.display = (isVir(t)) ? "none":"inline"; // hide reversed for virtual + gId("dig"+n+"s").style.display = (isVir(t) || isAna(t)) ? "none":"inline"; // hide skip 1st for virtual & analog + gId("dig"+n+"f").style.display = (isDig(t) || (isPWM(t) && maxL>2048)) ? "inline":"none"; // hide refresh (PWM hijacks reffresh for dithering on ESP32) + gId("dig"+n+"a").style.display = (hasW(t)) ? "inline":"none"; // auto calculate white + gId("dig"+n+"l").style.display = (isD2P(t) || isPWM(t)) ? "inline":"none"; // bus clock speed / PWM speed (relative) (not On/Off) + gId("rev"+n).innerHTML = isAna(t) ? "Inverted output":"Reversed"; // change reverse text for analog else (rotated 180°) + gId("net"+n+"h").style.display = isNet(t) && !is8266() ? "block" : "none"; // show host field for network types except on ESP8266 + if (!isNet(t) || is8266()) gN("HS"+n).value = ""; // cleart host field if not network type or ESP8266 }); }