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]
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 5fb3bb0a1d..42f082a36c 100644
--- a/wled00/data/pixelforge/pixelforge.htm
+++ b/wled00/data/pixelforge/pixelforge.htm
@@ -391,30 +391,9 @@
Available Tokens
Not available in 1D
-
-
-
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,12 @@
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
+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
+// 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 +444,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 fsMem(); // show file system memory info
+ await fsMem(); // update & show file system memory info, also updates wled version (wv)
+ await loadTools(); // load additional tools list from pftools.json
}
/* 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 +559,14 @@
}catch(e){msg('Download failed','err');}
menuClose();
}
-async function imgDel(){
- if(!confirm(`Delete ${sI.name}?`))return;
+
+async function deleteFile(name){
+ 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();
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); // 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();}
- 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 +1265,6 @@