Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions wled00/data/index.htm
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,9 @@
<button class="btn ibtn" onclick="toggleNodes()">Instance List</button>
<button class="btn ibtn" onclick="window.open(getURL('/update'),'_self');">Update WLED</button>
<button class="btn ibtn" id="resetbtn" onclick="cnfReset()">Reboot WLED</button>
<button class="btn infobtn" id="genPresets" onclick="genPresets()">Generate presets</button>
<button class="btn infobtn" id="savePresetsGen" onclick="savePresetsGen()" hidden="true">Save generated presets</button>
<textarea id="presetsGen" hidden="true"></textarea>
</div>
<br>
<span class="h">Made with&#32;<span id="heart">&#10084;&#xFE0E;</span>&#32;by&#32;<a href="https://github.com/Aircoookie/" target="_blank">Aircoookie</a>&#32;and the&#32;<a href="https://wled.discourse.group/" target="_blank">WLED community</a></span>
Expand Down
128 changes: 128 additions & 0 deletions wled00/data/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3306,6 +3306,134 @@ function simplifyUI() {
gId("btns").style.display = "none";
}


function genPresets()
{
var result = "";
var sep = "{";

var effects = eJson;
var playlistPS = JSON.parse("{}");
var playlistSep = JSON.parse("{}");
var playlistDur = JSON.parse("{}");
var playlistTrans = JSON.parse("{}");
var playlistQL = JSON.parse("{}");
function addToPlaylist(m, id, ql = undefined) {
if (!playlistPS[m]) playlistPS[m] = "";
if (!playlistDur[m]) playlistDur[m] = "";
if (!playlistTrans[m]) playlistTrans[m] = "";
if (!playlistSep[m]) playlistSep[m] = "";
playlistPS[m] += playlistSep[m] + `${id}`;
playlistDur[m] += playlistSep[m] + "100";
playlistTrans[m] += playlistSep[m] + "7";
playlistSep[m] = ",";
if(ql) playlistQL[m] = `${ql}`;
}
var seq=230; //Playlist start here
for (let ef of effects) {
if (ef.name.indexOf("RSVD") < 0) {
if (Array.isArray(fxdata) && fxdata.length>ef.id) {
let fd = fxdata[ef.id];
let eP = (fd == '')?[]:fd.split(";"); // effect parameters
let m = (eP.length<4 || eP[3]==='')?'1':eP[3]; // flags
// console.log(ef, eP);
//transform key values in json format
var defaultString = "";
//if key/values defined, convert them to json in defaultString
if (eP.length>4) {
let defaults = (eP[4] == '')?[]:eP[4].split(",");
for (let i=0; i<defaults.length;i++) {
let keyValue = (defaults[i] == '')?[]:defaults[i].split("=");
defaultString += `,"${keyValue[0]}":${keyValue[1]}`;
}
}
//if not defined set to default
if (!defaultString.includes("sx")) defaultString += ',"sx":128'; //Speed
if (!defaultString.includes("ix")) defaultString += ',"ix":128'; //Intensity
if (!defaultString.includes("c1")) defaultString += ',"c1":128'; //Custom 1
if (!defaultString.includes("c2")) defaultString += ',"c2":128'; //Custom 2
if (!defaultString.includes("c3")) defaultString += ',"c3":16'; //Custom 3
if (!defaultString.includes("o1")) defaultString += ',"o1":0'; //Check 1
if (!defaultString.includes("o2")) defaultString += ',"o2":0'; //Check 2
if (!defaultString.includes("o3")) defaultString += ',"o3":0'; //Check 3
if (!defaultString.includes("pal")) defaultString += ',"pal":11'; //Temporary for deterministic effects test: Set to 11/Raibow instead of 1/Random smooth palette (if not set different)
if (!defaultString.includes("m12") && m.includes("1") && !m.includes("1.5") && !m.includes("12"))
defaultString += ',"rev":true,"mi":true,"rY":true,"mY":true,"m12":2'; //Arc expansion
else {
if (!defaultString.includes("rev")) defaultString += ',"rev":false';
if (!defaultString.includes("mi")) defaultString += ',"mi":false';
if (!defaultString.includes("rY")) defaultString += ',"rY":false';
if (!defaultString.includes("mY")) defaultString += ',"mY":false';
}
result += `${sep}"${ef.id}":{"n":"${ef.name}","mainseg":0,"seg":[{"id":0,"fx":${ef.id}${defaultString}}]}`;
sep = "\n,";
if(m.length <= 3) {
addToPlaylist(m, ef.id, m);
}
else {
addToPlaylist(m, ef.id);
}
addToPlaylist("All", ef.id, "ALL");
if(ef.name.startsWith("Y💡")) addToPlaylist("AnimARTrix", ef.id, "AM");
if (m.includes("1")) addToPlaylist("All 1D", ef.id, "1D");
if (m.includes("2")) addToPlaylist("All 2D", ef.id, "2D");

seq = Math.max(seq, (parseInt(ef.id) + 1));
} //fxdata is array
} //not RSVD
} //all effects

// console.log(playlistPS, playlistDur, playlistTrans);
for (const m in playlistPS) {
if(!playlistQL[m]) playlistQL[m] = seq;
let playListString = `\n,"${seq}":{"n":"${m} Playlist","ql":"${playlistQL[m]}","on":true,"playlist":{"ps":[${playlistPS[m]}],"dur":[${playlistDur[m]}],"transition":[${playlistTrans[m]}],"repeat":0,"end":0,"r":1}}`;
// console.log(playListString);
result += playListString;
seq++;
}

result += "}";

//assign result and show text and save button
gId("genPresets").hidden = true;
gId("savePresetsGen").hidden = false;
gId("presetsGen").hidden = false;
gId("presetsGen").value = result;
// console.log(result);

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Build the presets object first, then JSON.stringify – string-building is brittle

genPresets() manually concatenates JSON with lots of +=, new-line commas, and string escaping assumptions:

  • An effect name containing " or \ will break the produced JSON.
  • Re-checking presence with defaultString.includes("sx") is error-prone ("o1" matches "o10").
  • Manual separators (sep, playlistSep) are easy to mis-place and hard to maintain.

Consider:

-function genPresets() {
-  /* huge string-building logic */
-}
+function genPresets() {
+  const presets = {};
+  const playlists = {};
+
+  // 1. Generate preset objects -------------------------------------------
+  eJson.forEach(([id, name]) => {
+    if (name.includes('RSVD')) return;
+
+    const p = { n: name, mainseg: 0, seg: [{ id: 0, fx: id }] };
+    Object.assign(p.seg[0], defaultFxParams(id)); // helper that returns the sx/ix/… object
+    presets[id] = p;
+
+    tagPlaylists(playlists, id, name);           // helper that fills playlists hash
+  });
+
+  // 2. Convert playlists hash → preset-style objects ----------------------
+  let nextId = Math.max(...Object.keys(presets).map(Number)) + 1;
+  for (const [tag, { ps, dur, transition, ql }] of Object.entries(playlists)) {
+    presets[nextId] = {
+      n: `${tag} Playlist`,
+      ql,
+      on: true,
+      playlist: { ps, dur, transition, repeat: 0, end: 0, r: 1 }
+    };
+    nextId++;
+  }
+
+  // 3. Show result --------------------------------------------------------
+  gId('presetsGen').value = JSON.stringify(presets, null, 2);
+  gId('genPresets').hidden = true;
+  gId('savePresetsGen').hidden = false;
+  gId('presetsGen').hidden = false;
+}

Benefits:

  • Guaranteed valid JSON – no manual escaping.
  • Logic is decomposed into helpers (defaultFxParams, tagPlaylists) improving readability & testability.
  • Future fields are added by editing objects, not string replacements.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In wled00/data/index.js from lines 3309 to 3404, the genPresets function builds
JSON by manually concatenating strings, which risks invalid JSON if effect names
contain special characters and is error-prone due to substring checks and manual
separators. Refactor genPresets to first construct a JavaScript object
representing the presets and playlists, using helper functions for default
parameters and playlist tagging, then convert this object to a JSON string with
JSON.stringify. This ensures valid JSON output, simplifies the logic, and
improves maintainability.



function uploadFileWithText(name, text)
{
var req = new XMLHttpRequest();
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
req.addEventListener('error', function(e){showToast(e.stack,true);});
req.open("POST", "/upload");
var formData = new FormData();

var blob = new Blob([text], {type : 'application/text'});
var fileOfBlob = new File([blob], name);
formData.append("upload", fileOfBlob);

req.send(formData);
}

function savePresetsGen()
{
if (!confirm('Are you sure to (over)write presets.json?')) return;

uploadFileWithText("/presets.json", gId("presetsGen").value);
}

function savePresetsGen()
{
if (!confirm('Are you sure to (over)write presets.json?')) return;

uploadFileWithText("/presets.json", gId("presetsGen").value);
}


size();
_C.style.setProperty('--n', N);

Expand Down