From 30968b3d923dbd285e368da585b0104cb290cff6 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 5 Mar 2026 15:18:58 +0100 Subject: [PATCH 01/10] load pixel forge tools from json file, bugfixes - load available external tools from a json file instead of hard-coding them in - add buttons to update or delete tools (instead of manually deleting files in /edit) - fixed bug that file system memory was not updated after uploading or deleting files - fixed a bug where gifs with a space would not display - fixed bug of page not loading properly (out of sync access to tabSw() ) --- wled00/data/pixelforge/pixelforge.htm | 179 ++++++++++++++++---------- wled00/wled_server.cpp | 2 + 2 files changed, 115 insertions(+), 66 deletions(-) diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 5fb3bb0a1d..7df813c931 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -391,30 +391,9 @@

Available Tokens

-
-
-

Pixel Paint

-
Interactive painting tool
- -
-
-
-
-
-

Video Lab

-
Stream video and generate animated GIFs (beta)
- -
-
-
-
-
-

PIXEL MAGIC Tool

-
Legacy pixel art editor
- -
-
-
+
+
Loading tools...
+
@@ -442,6 +421,11 @@

PIXEL MAGIC Tool

let iL=[]; // image list let gF=null,gI=null,aT=null; let fL; // file list +let pT = []; // local tools list from JSON +const remoteURL = 'https://dedehai.github.io/pf_tools.json'; // Change to your actual repo +const toolsjson = 'pf_tools.json'; +// note: the pf_tools.json must use major.minor for tool versions (e.g. 0.95 or 1.1), otherwise the update check won't work +// also the code assumes that the tool url points to a gz file // load external resources in sequence to avoid 503 errors if heap is low, repeats indefinitely until loaded (function loadFiles() { @@ -459,21 +443,19 @@

PIXEL MAGIC Tool

getLoc(); // create off screen canvas rv = cE('canvas'); - rvc = rv.getContext('2d',{willReadFrequently:true}); + rvc = rv.getContext('2d',{willReadFrequently:true}); rv.width = cv.width; rv.height = cv.height; - + tabSw(localStorage.tab||'img'); // switch to last open tab or image tab by default await segLoad(); // load available segments await flU(); // update file list - toolChk('pixelpaint.htm','t1'); // update buttons of additional tools - toolChk('videolab.htm','t2'); - toolChk('pxmagic.htm','t3'); + await loadTools(); // load additional tools list from pf_tools.json await fsMem(); // show file system memory info } /* update file list */ async function flU(){ try{ - const r = await fetch(getURL('/edit?list=/')); + const r = await fetch(getURL('/edit?list=/&cb=' + Date.now())); fL = await r.json(); }catch(e){console.error(e);} } @@ -576,14 +558,14 @@

PIXEL MAGIC Tool

await new Promise(res=>{ const im=new Image(); im.onload=()=>{ - it.style.backgroundImage=`url(${url}?cb=${Date.now()})`; + it.style.backgroundImage=`url('${encodeURI(url)}?cb=${Date.now()}')`; if(!isGif) it.style.border="5px solid red"; it.classList.remove('loading'); res(); const kb=Math.round(f.size/1024); it.title=`${name}\n${im.width}x${im.height}\n${kb} KB`; }; im.onerror=()=>{it.classList.remove('loading');it.style.background='#222';res();}; - im.src=url+'?cb='+Date.now(); + im.src=encodeURI(url)+'?cb='+Date.now(); }); } } @@ -597,35 +579,93 @@

PIXEL MAGIC Tool

//} } -/* additional tools: check if present, install if not */ -function toolChk(file, btnId) { +/* additional tools: loaded from pf_tools.json, store json locally for offline use*/ +async function loadTools() { try { - const has = fL.some(f => f.name.includes(file)); - const b = getId(btnId); - b.style.display = 'block'; - b.style.margin = '10px auto'; - if (has) { - b.textContent = 'Open'; - b.onclick = () => window.open(getURL(`/${file}`), '_blank'); // open tool: remove gz to not trigger download - } else { - b.textContent = 'Download'; - b.onclick = async () => { - const fileGz = file + '.gz'; // use gz version - const url = `https://dedehai.github.io/${fileGz}`; // always download gz version - if (!confirm(`Download ${url}?`)) return; - try { - const f = await fetch(url); - if (!f.ok) throw new Error("Download failed " + f.status); - const blob = await f.blob(), fd = new FormData(); - fd.append("data", blob, fileGz); - const u = await fetch(getURL("/upload"), { method: "POST", body: fd }); - alert(u.ok ? "Tool installed!" : "Upload failed"); - await flU(); // update file list - toolChk(file, btnId); // re-check and update button (must pass non-gz file name) - } catch (e) { alert("Error " + e.message); } - }; + const res = await fetch(getURL('/' + toolsjson + '?cb=' + Date.now())); // load local tools list + pT = res.ok ? await res.json() : []; + } catch (e) {} + + renderTools(); // render whatever we have + + try { + const rT = await (await fetch(remoteURL + '?cb=' + Date.now())).json(); + let changed = false; + rT.forEach(rt => { + let lt = pT.find(t => t.id === rt.id); + if (!lt) { + pT.push(rt); // new tool available + changed = true; + } else { + // check version + if (isNewer(rt.ver, lt.ver)) { + lt.upv = rt.ver; + changed = true; + } else if (lt.upv) { + delete lt.upv; // clean up flag if user updated or remote rolled back + changed = true; + } + lt.source = rt.source; lt.desc = rt.desc; // update info in case it changed + } + }); + if (changed) { + await saveToolsjson(); // save updated json + renderTools(); } - } catch (e) { console.error(e); } + } catch(e){console.error(e);} +} + +async function saveToolsjson() { + const fd = new FormData(); + fd.append("data", new Blob([JSON.stringify(pT)], {type:'application/json'}), toolsjson); + await fetch(getURL("/upload"), { method: "POST", body: fd }); +} + +// tool versions must be in format major.minor (e.g. 0.95 or 1.1) +function isNewer(vN, vO) { + return parseFloat(vN) > parseFloat(vO); +} + +function renderTools() { + let h = ''; + pT.forEach(t => { + const installed = fL.some(f => f.name.includes(t.file)); // check if tool file exists (either .htm or .htm.gz) + h += `
+
+

${t.name} v${t.ver}

+ ${installed ? `` : ''} +
+ ${t.desc} +
+ by ${t.author} | ${t.source} +
+
+ ${installed ? `` : ``} + ${t.upv && installed ? `` : ''} +
+
`; + }); + getId('tools').innerHTML = h || 'No tools found (offline?).'; +} + +// install or update tool +async function insT(id) { + const t = pT.find(x => x.id == id); + ovShow(); + try { + const f = await fetch(t.url); // url in json must be pointing to a gz file + if (!f.ok) throw new Error("Download failed " + f.status); + const fd = new FormData(); + fd.append("data", await f.blob(), t.file + '.gz'); // always use gz for file name (source MUST be gz) + const u = await fetch(getURL("/upload"), { method: "POST", body: fd }); + alert(u.ok ? "Tool installed!" : "Install failed"); + if (t.upv) { t.ver = t.upv; delete t.upv; } // remove update flag + await saveToolsjson(); + await flU(); // refresh file list + renderTools(); + } catch(e) { alert("Error " + e.message); } + fsMem(); // refresh memory info after upload + ovHide(); } /* fs/mem info */ @@ -1107,6 +1147,7 @@

PIXEL MAGIC Tool

} catch (e) { msg(`Error: ${e.message}`, 'err'); } finally { + fsMem(); // refresh memory info after upload ovHide(); } }; @@ -1143,7 +1184,7 @@

PIXEL MAGIC Tool

m.style.left=x+'px'; m.style.top=y+'px'; m.innerHTML=` - `; + `; d.body.appendChild(m); setTimeout(()=>{ const h=e=>{ @@ -1163,16 +1204,23 @@

PIXEL MAGIC Tool

}catch(e){msg('Download failed','err');} menuClose(); } -async function imgDel(){ - if(!confirm(`Delete ${sI.name}?`))return; + +async function deleteFile(name){ + const ins = fL.some(f => f.name.includes(name)); + if (fL.some(f => f.name === `${name}.gz`)) + name += '.gz'; // if .gz version of file exists, delete that (handles tools which are stored gzipped on device) + if(!confirm(`Delete ${name}?`))return; ovShow(); try{ - const r = await fetch(getURL(`/edit?func=delete&path=/${sI.name}`)); - if(r.ok){ msg('Deleted'); imgRm(sI.name); } + const r = await fetch(getURL(`/edit?func=delete&path=/${name}`)); + if(r.ok){ msg('Deleted'); imgRm(name); } else msg('Delete failed! File in use?','err'); }catch(e){msg('Delete failed','err');} finally{ovHide();} - menuClose(); + fsMem(); // refresh memory info after delete + menuClose(); // close menu (used for image delete button) + await flU(); // update file list + renderTools(); // re-render tools list } /* tab select and additional tools */ @@ -1186,7 +1234,6 @@

PIXEL MAGIC Tool

'Img,Txt,Oth'.split(',').forEach((s,i)=>{ getId('t'+s).onclick=()=>tabSw(['img','txt','oth'][i]); }); -tabSw(localStorage.tab||'img'); /* tokens insert */ function txtIns(el,t){ diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 79e71af54e..d002565f45 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -221,6 +221,7 @@ static void handleUpload(AsyncWebServerRequest *request, const String& filename, request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File Uploaded!")); } cacheInvalidate++; + updateFSInfo(); // refresh memory usage info } } @@ -310,6 +311,7 @@ static void createEditHandler() { request->send(500, FPSTR(CONTENT_TYPE_PLAIN), F("Delete failed")); else request->send(200, FPSTR(CONTENT_TYPE_PLAIN), F("File deleted")); + updateFSInfo(); // refresh memory usage info return; } From 34bcf01af649b07a9f13b1b28a03baaf81ed91eb Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 5 Mar 2026 20:13:55 +0100 Subject: [PATCH 02/10] fix rabbit suggestions --- wled00/data/common.js | 4 +++ wled00/data/pixelforge/pixelforge.htm | 36 ++++++++++++++++----------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/wled00/data/common.js b/wled00/data/common.js index a6223daa7c..0b66ca1a78 100644 --- a/wled00/data/common.js +++ b/wled00/data/common.js @@ -126,6 +126,10 @@ function getLoc() { } } function getURL(path) { return (loc ? locproto + "//" + locip : "") + path; } +// HTML entity escaper – use on any remote/user-supplied text inserted into innerHTML +function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } +// URL sanitizer – blocks javascript: and data: URIs, use for externally supplied URLs for some basic safety +function safeUrl(u) { return /^https?:\/\//.test(u) ? u : '#'; } function B() { window.open(getURL("/settings"),"_self"); } var timeout; function showToast(text, error = false) { diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 7df813c931..013bb6f19b 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -391,9 +391,9 @@

Available Tokens

-
-
Loading tools...
-
+
+
Loading tools...
+
@@ -599,13 +599,9 @@

Available Tokens

} else { // check version if (isNewer(rt.ver, lt.ver)) { - lt.upv = rt.ver; - changed = true; - } else if (lt.upv) { - delete lt.upv; // clean up flag if user updated or remote rolled back + lt.pending = { ver: rt.ver, url: rt.url, file: rt.file, desc: rt.desc }; // add pending update info changed = true; } - lt.source = rt.source; lt.desc = rt.desc; // update info in case it changed } }); if (changed) { @@ -632,16 +628,16 @@

Available Tokens

const installed = fL.some(f => f.name.includes(t.file)); // check if tool file exists (either .htm or .htm.gz) h += `
-

${t.name} v${t.ver}

+

${esc(t.name)} v${t.ver}

${installed ? `` : ''}
${t.desc}
- by ${t.author} | ${t.source} + by ${esc(t.author)} | ${safeUrl(t.source)}
${installed ? `` : ``} - ${t.upv && installed ? `` : ''} + ${t.pending && installed ? `` : ''}
`; }); @@ -659,7 +655,14 @@

${t.name} v${t.ver}

fd.append("data", await f.blob(), t.file + '.gz'); // always use gz for file name (source MUST be gz) const u = await fetch(getURL("/upload"), { method: "POST", body: fd }); alert(u.ok ? "Tool installed!" : "Install failed"); - if (t.upv) { t.ver = t.upv; delete t.upv; } // remove update flag + if (u.ok && t.pending) { + // save and remove update info after successful update + t.ver = t.pending.ver; + t.url = t.pending.url; + t.file = t.pending.file; + t.desc = t.pending.desc; + delete t.pending; + } await saveToolsjson(); await flU(); // refresh file list renderTools(); @@ -1184,7 +1187,7 @@

${t.name} v${t.ver}

m.style.left=x+'px'; m.style.top=y+'px'; m.innerHTML=` - `; + `; d.body.appendChild(m); setTimeout(()=>{ const h=e=>{ @@ -1206,14 +1209,17 @@

${t.name} v${t.ver}

} async function deleteFile(name){ - const ins = fL.some(f => f.name.includes(name)); + name=name.replace('/',''); // remove leading slash if present (just in case) if (fL.some(f => f.name === `${name}.gz`)) name += '.gz'; // if .gz version of file exists, delete that (handles tools which are stored gzipped on device) if(!confirm(`Delete ${name}?`))return; ovShow(); try{ const r = await fetch(getURL(`/edit?func=delete&path=/${name}`)); - if(r.ok){ msg('Deleted'); imgRm(name); } + if(r.ok){ + msg('Deleted'); + imgRm(name); // remove image from grid (if this was not an image, this does nothing) + } else msg('Delete failed! File in use?','err'); }catch(e){msg('Delete failed','err');} finally{ovHide();} From cff13fbd991efb8280c8aa70b525cbfac53de4da Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 5 Mar 2026 20:34:30 +0100 Subject: [PATCH 03/10] minor fixes, indentation --- wled00/data/pixelforge/pixelforge.htm | 59 ++++++++++++++------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 013bb6f19b..27fc7d57bd 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -612,14 +612,14 @@

Available Tokens

} async function saveToolsjson() { - const fd = new FormData(); - fd.append("data", new Blob([JSON.stringify(pT)], {type:'application/json'}), toolsjson); - await fetch(getURL("/upload"), { method: "POST", body: fd }); + const fd = new FormData(); + fd.append("data", new Blob([JSON.stringify(pT)], {type:'application/json'}), toolsjson); + await fetch(getURL("/upload"), { method: "POST", body: fd }); } // tool versions must be in format major.minor (e.g. 0.95 or 1.1) function isNewer(vN, vO) { - return parseFloat(vN) > parseFloat(vO); + return parseFloat(vN) > parseFloat(vO); } function renderTools() { @@ -646,29 +646,30 @@

${esc(t.name)} v${t.ver}

// install or update tool async function insT(id) { - const t = pT.find(x => x.id == id); - ovShow(); - try { - const f = await fetch(t.url); // url in json must be pointing to a gz file - if (!f.ok) throw new Error("Download failed " + f.status); - const fd = new FormData(); - fd.append("data", await f.blob(), t.file + '.gz'); // always use gz for file name (source MUST be gz) - const u = await fetch(getURL("/upload"), { method: "POST", body: fd }); - alert(u.ok ? "Tool installed!" : "Install failed"); - if (u.ok && t.pending) { - // save and remove update info after successful update - t.ver = t.pending.ver; - t.url = t.pending.url; - t.file = t.pending.file; - t.desc = t.pending.desc; - delete t.pending; - } - await saveToolsjson(); - await flU(); // refresh file list - renderTools(); - } catch(e) { alert("Error " + e.message); } - fsMem(); // refresh memory info after upload - ovHide(); + const t = pT.find(x => x.id == id); + ovShow(); + try { + const src = t.pending || t; + const f = await fetch(src.url); // url in json must be pointing to a gz file + if (!f.ok) throw new Error("Download failed " + f.status); + const fd = new FormData(); + fd.append("data", await f.blob(), src.file + '.gz'); // always use gz for file name (source MUST be gz) + const u = await fetch(getURL("/upload"), { method: "POST", body: fd }); + alert(u.ok ? "Tool installed!" : "Install failed"); + if (u.ok && t.pending) { + // save and remove update info after successful update + t.ver = t.pending.ver; + t.url = t.pending.url; + t.file = t.pending.file; + t.desc = t.pending.desc; + delete t.pending; + } + await saveToolsjson(); + await flU(); // refresh file list + renderTools(); + } catch(e) { alert("Error " + e.message); } + fsMem(); // refresh memory info after upload + ovHide(); } /* fs/mem info */ @@ -1209,8 +1210,8 @@

${esc(t.name)} v${t.ver}

} async function deleteFile(name){ - name=name.replace('/',''); // remove leading slash if present (just in case) - if (fL.some(f => f.name === `${name}.gz`)) + name = name.replace('/',''); // remove leading slash if present (just in case) + if (fL.some(f => f.name.replace('/','') === `${name}.gz`)) name += '.gz'; // if .gz version of file exists, delete that (handles tools which are stored gzipped on device) if(!confirm(`Delete ${name}?`))return; ovShow(); From ec59000be07e2cc06af0a336b971e87df9ea376b Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 5 Mar 2026 20:44:26 +0100 Subject: [PATCH 04/10] improve robustness --- wled00/data/pixelforge/pixelforge.htm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 27fc7d57bd..1e1a5308db 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -629,15 +629,15 @@

Available Tokens

h += `

${esc(t.name)} v${t.ver}

- ${installed ? `` : ''} + ${installed ? `` : ''}
${t.desc}
by ${esc(t.author)} | ${safeUrl(t.source)}
- ${installed ? `` : ``} - ${t.pending && installed ? `` : ''} + ${installed ? `` : ``} + ${t.pending && installed ? `` : ''}
`; }); From 4acfc7bf58dee8d5d50fc5bc108276b0847c61ec Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Mar 2026 11:47:52 +0100 Subject: [PATCH 05/10] rename pf_tools.json to pftools.jon --- wled00/data/pixelforge/pixelforge.htm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 1e1a5308db..336c8fe60b 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -422,9 +422,9 @@

Available Tokens

let gF=null,gI=null,aT=null; let fL; // file list let pT = []; // local tools list from JSON -const remoteURL = 'https://dedehai.github.io/pf_tools.json'; // Change to your actual repo -const toolsjson = 'pf_tools.json'; -// note: the pf_tools.json must use major.minor for tool versions (e.g. 0.95 or 1.1), otherwise the update check won't work +const remoteURL = 'https://dedehai.github.io/pftools.json'; // Change to your actual repo +const toolsjson = 'pftools.json'; +// note: the pftools.json must use major.minor for tool versions (e.g. 0.95 or 1.1), otherwise the update check won't work // also the code assumes that the tool url points to a gz file // load external resources in sequence to avoid 503 errors if heap is low, repeats indefinitely until loaded @@ -448,7 +448,7 @@

Available Tokens

tabSw(localStorage.tab||'img'); // switch to last open tab or image tab by default await segLoad(); // load available segments await flU(); // update file list - await loadTools(); // load additional tools list from pf_tools.json + await loadTools(); // load additional tools list from pftools.json await fsMem(); // show file system memory info } @@ -579,7 +579,7 @@

Available Tokens

//} } -/* additional tools: loaded from pf_tools.json, store json locally for offline use*/ +/* additional tools: loaded from pftools.json, store json locally for offline use*/ async function loadTools() { try { const res = await fetch(getURL('/' + toolsjson + '?cb=' + Date.now())); // load local tools list From 2497ae1a18919485e484080cec7954c4b8c4ea89 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Mar 2026 15:29:57 +0100 Subject: [PATCH 06/10] add simple wled version check for tools in case of future tool incompatibilites --- wled00/data/pixelforge/pixelforge.htm | 46 +++++++++++++++++++++------ 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 336c8fe60b..c8da6460bc 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -422,6 +422,7 @@

Available Tokens

let gF=null,gI=null,aT=null; let fL; // file list let pT = []; // local tools list from JSON +let wv = [0, 0]; // wled version [major, minor], updated in fsMem(), used to check tool compatibility const remoteURL = 'https://dedehai.github.io/pftools.json'; // Change to your actual repo const toolsjson = 'pftools.json'; // note: the pftools.json must use major.minor for tool versions (e.g. 0.95 or 1.1), otherwise the update check won't work @@ -448,8 +449,8 @@

Available Tokens

tabSw(localStorage.tab||'img'); // switch to last open tab or image tab by default await segLoad(); // load available segments await flU(); // update file list + await fsMem(); // update & show file system memory info, also updates wled version (wv) await loadTools(); // load additional tools list from pftools.json - await fsMem(); // show file system memory info } /* update file list */ @@ -579,6 +580,14 @@

Available Tokens

//} } +// check for tool compatibility with current wled version (wled_min requirement in pftools.json) +function compTool(t) { + if (!t.wled_min || !wv) return true; + const m = t.wled_min.match(/\d+/g); + const a = wv, b = [m[0]|0, m[1]|0]; + return a[0] > b[0] || (a[0] === b[0] && a[1] >= b[1]); +} + /* additional tools: loaded from pftools.json, store json locally for offline use*/ async function loadTools() { try { @@ -625,20 +634,31 @@

Available Tokens

function renderTools() { let h = ''; pT.forEach(t => { + const compatible = compTool(t); // check if compatible with current wled version const installed = fL.some(f => f.name.includes(t.file)); // check if tool file exists (either .htm or .htm.gz) h += `

${esc(t.name)} v${t.ver}

${installed ? `` : ''}
- ${t.desc} + ${esc(t.desc)}
by ${esc(t.author)} | ${safeUrl(t.source)}
-
- ${installed ? `` : ``} - ${t.pending && installed ? `` : ''} -
+ ${ + compatible ? + `
+ ${installed ? + `` + : `` + } + ${t.pending && installed ? + `` + : '' + } +
` + : `
Requires WLED ${t.wled_min}
` + }
`; }); getId('tools').innerHTML = h || 'No tools found (offline?).'; @@ -672,14 +692,20 @@

${esc(t.name)} v${t.ver}

ovHide(); } -/* fs/mem info */ +/* fs/mem info & wled version */ async function fsMem(){ try{ const r=await fetch(getURL('/json/info')); const info=await r.json(); - if(info&&info.fs){ - getId("mem").textContent=`by @dedehai | Memory: ${info.fs.u} KB / ${info.fs.t} KB`; - getId("mem").style.display="block"; + if (info){ + if (info.fs) { + getId("mem").textContent=`by @dedehai | Memory: ${info.fs.u} KB / ${info.fs.t} KB`; + getId("mem").style.display="block"; + } + if (info.ver) { + const m = info.ver.match(/\d+/g); // extract all numbers from version string (e.g. "16.1.0-beta" → [16, 1]) + wv = [parseInt(m[0]) || 0, parseInt(m[1]) || 0]; + } } }catch(e){console.error(e);} } From c2606935811e14b97c2035fd227f0fea9377f895 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Sun, 22 Mar 2026 15:37:49 +0100 Subject: [PATCH 07/10] minor fix --- wled00/data/pixelforge/pixelforge.htm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index c8da6460bc..12d1d18942 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -641,7 +641,7 @@

Available Tokens

${esc(t.name)} v${t.ver}

${installed ? `` : ''}
- ${esc(t.desc)} + ${t.desc}
by ${esc(t.author)} | ${safeUrl(t.source)}
@@ -653,7 +653,7 @@

${esc(t.name)} v${t.ver}

: `` } ${t.pending && installed ? - `` + `` : '' } ` From 0b8ed93d958f0714b3d865b2384275e54208f815 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Wed, 25 Mar 2026 09:26:21 +0100 Subject: [PATCH 08/10] fix rabbit suggestion: carry over full manifest info --- wled00/data/pixelforge/pixelforge.htm | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/wled00/data/pixelforge/pixelforge.htm b/wled00/data/pixelforge/pixelforge.htm index 12d1d18942..42f082a36c 100644 --- a/wled00/data/pixelforge/pixelforge.htm +++ b/wled00/data/pixelforge/pixelforge.htm @@ -608,7 +608,7 @@

Available Tokens

} else { // check version if (isNewer(rt.ver, lt.ver)) { - lt.pending = { ver: rt.ver, url: rt.url, file: rt.file, desc: rt.desc }; // add pending update info + lt.pending = rt; // mark update as pending, keep old info until user clicks update button changed = true; } } @@ -634,11 +634,12 @@

Available Tokens

function renderTools() { let h = ''; pT.forEach(t => { - const compatible = compTool(t); // check if compatible with current wled version const installed = fL.some(f => f.name.includes(t.file)); // check if tool file exists (either .htm or .htm.gz) + const target = (installed && t.pending) ? t.pending : t; // if update pending and tool is installed, show update info + const compatible = compTool(target); // check if compatible with current wled version h += `
-

${esc(t.name)} v${t.ver}

+

${esc(t.name)} v${esc(t.ver)}

${installed ? `` : ''}
${t.desc} @@ -657,7 +658,7 @@

${esc(t.name)} v${t.ver}

: '' }
` - : `
Requires WLED ${t.wled_min}
` + : `
Requires WLED ${esc(target.wled_min)}
` } `; }); @@ -678,11 +679,8 @@

${esc(t.name)} v${t.ver}

alert(u.ok ? "Tool installed!" : "Install failed"); if (u.ok && t.pending) { // save and remove update info after successful update - t.ver = t.pending.ver; - t.url = t.pending.url; - t.file = t.pending.file; - t.desc = t.pending.desc; - delete t.pending; + Object.assign(t, t.pending); + delete t.pending; } await saveToolsjson(); await flU(); // refresh file list From e3b075195584630d48faf74d95f6e17f9d82d288 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 26 Mar 2026 10:02:51 +0100 Subject: [PATCH 09/10] disable pixelforge on 1m esp8266 builds --- platformio.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platformio.ini b/platformio.ini index 60dedd473b..16f990a60c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -431,6 +431,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME= ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM -D WLED_DISABLE_PARTICLESYSTEM1D -D WLED_DISABLE_PARTICLESYSTEM2D + -D WLED_DISABLE_PIXELFORGE lib_deps = ${esp8266.lib_deps} [env:esp01_1m_full_compat] @@ -441,6 +442,7 @@ platform_packages = ${esp8266.platform_packages_compat} build_flags = ${common.build_flags} ${esp8266.build_flags_compat} -D WLED_RELEASE_NAME=\"ESP01_compat\" -D WLED_DISABLE_OTA #-DWLED_DISABLE_2D -D WLED_DISABLE_PARTICLESYSTEM1D -D WLED_DISABLE_PARTICLESYSTEM2D + -D WLED_DISABLE_PIXELFORGE [env:esp01_1m_full_160] extends = env:esp01_1m_full @@ -449,6 +451,7 @@ build_flags = ${common.build_flags} ${esp8266.build_flags} -D WLED_RELEASE_NAME= ; -D WLED_USE_REAL_MATH ;; may fix wrong sunset/sunrise times, at the cost of 7064 bytes FLASH and 975 bytes RAM -D WLED_DISABLE_PARTICLESYSTEM1D -D WLED_DISABLE_PARTICLESYSTEM2D + -D WLED_DISABLE_PIXELFORGE custom_usermods = audioreactive [env:esp32dev] From 9dd5504565fe440db9450b01e0e7600ede4cdf87 Mon Sep 17 00:00:00 2001 From: Damian Schneider Date: Thu, 26 Mar 2026 10:17:29 +0100 Subject: [PATCH 10/10] add inline html page to avoid 404 --- wled00/wled_server.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index d002565f45..f7aac7fa50 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -636,6 +636,14 @@ void initServer() server.on(_pixelforge_htm, HTTP_GET, [](AsyncWebServerRequest *request) { handleStaticContent(request, FPSTR(_pixelforge_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixelforge, PAGE_pixelforge_length); }); + #else + server.on("/pixelforge.htm", HTTP_GET, [](AsyncWebServerRequest *request) { + request->send(200, "text/html", + F("PixelForge" + "" + "

Sorry, PixelForge is not supported in this build.

") + ); + }); #endif #endif