Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions tools/cdata.js
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,12 @@ writeChunks(
name: "PAGE_settings_pininfo",
method: "gzip",
filter: "html-minify"
},
{
file: "settings_fx.htm",
name: "PAGE_settings_fx",
method: "gzip",
filter: "html-minify"
}
],
"wled00/html_settings.h"
Expand Down
7 changes: 4 additions & 3 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -558,10 +558,11 @@ Segment &Segment::setOption(uint8_t n, bool val) {
}

Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
// skip reserved
while (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4) == 0) fx++;
// skip reserved or user-hidden effects
while (fx < strip.getModeCount() &&
(strncmp_P("RSVD", strip.getModeData(fx), 4) == 0 || isFxHidden(fx))) fx++;
if (fx >= strip.getModeCount()) fx = 0; // set solid mode
// if we have a valid mode & is not reserved
// if we have a valid mode
if (fx != mode) {
startTransition(strip.getTransition(), true); // set effect transitions (must create segment copy)
mode = fx;
Expand Down
17 changes: 17 additions & 0 deletions wled00/cfg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,17 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
needsSave = !UsermodManager::readFromConfig(usermods_settings);
}

// hidden effects: load id list into bitmap
JsonObject fx = doc["fx"];
JsonArray fxHidden = fx["hidden"];
if (!fxHidden.isNull()) {
for (size_t i = 0; i < 8; i++) hiddenFxMask[i] = 0;
for (JsonVariant v : fxHidden) {
uint8_t id = v.as<uint8_t>();
if (id != 0) setFxHidden(id, true); // never hide Solid (id 0)
}
Comment on lines +772 to +775
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard fx.hidden IDs before narrowing to uint8_t.

v.as<uint8_t>() can wrap out-of-range JSON values (e.g., 300 -> 44), hiding unintended effects.

Suggested fix
-    for (JsonVariant v : fxHidden) {
-      uint8_t id = v.as<uint8_t>();
-      if (id != 0) setFxHidden(id, true); // never hide Solid (id 0)
-    }
+    for (JsonVariant v : fxHidden) {
+      int id = v | -1;
+      if (id <= 0 || id > 255) continue; // skip invalid IDs; never hide Solid (0)
+      setFxHidden(static_cast<uint8_t>(id), true);
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/cfg.cpp` around lines 772 - 775, The loop that hides effects reads
JSON into a uint8_t via v.as<uint8_t>() which can wrap out-of-range values;
change the logic in the fxHidden iteration to first read the variant into a
signed integer (e.g., int or int32) or validate with v.is<int>(), then check
that the value is within the valid range (0 <= id <= 255) and skip or log
invalid IDs before narrowing; finally call setFxHidden(id, true) only after
confirming id is in-range and also preserve the existing special-case check to
never hide Solid (id 0). Use the identifiers fxHidden, JsonVariant v, and
setFxHidden to locate the code to update.

}

if (fromFS) return needsSave;
// if from /json/cfg
doReboot = doc[F("rb")] | doReboot;
Expand Down Expand Up @@ -1261,6 +1272,12 @@ void serializeConfig(JsonObject root) {
dmx[F("e131proxy")] = e131ProxyUniverse;
#endif

JsonObject fx = root.createNestedObject("fx");
JsonArray fxHidden = fx.createNestedArray("hidden");
uint16_t maxId = strip.getModeCount();
if (maxId > 256) maxId = 256;
for (uint16_t id = 1; id < maxId; id++) if (isFxHidden(id)) fxHidden.add((uint8_t)id);

JsonObject usermods_settings = root.createNestedObject("um");
UsermodManager::addToConfig(usermods_settings);
}
Expand Down
3 changes: 2 additions & 1 deletion wled00/const.h
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,8 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit");
#define SUBPAGE_UPDATE 9
#define SUBPAGE_2D 10
#define SUBPAGE_PINS 11
#define SUBPAGE_LAST SUBPAGE_PINS
#define SUBPAGE_FX 12
#define SUBPAGE_LAST SUBPAGE_FX
#define SUBPAGE_LOCK 251
#define SUBPAGE_PINREQ 252
#define SUBPAGE_CSS 253
Expand Down
1 change: 1 addition & 0 deletions wled00/data/settings.htm
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<button type="submit" onclick="window.location=getURL('/settings/pins')">Pin Info</button>
<button id="2dbtn" type="submit" onclick="window.location=getURL('/settings/2D')">2D Configuration</button>
<button type="submit" onclick="window.location=getURL('/settings/ui')">User Interface</button>
<button type="submit" onclick="window.location=getURL('/settings/fx')">Effect Visibility</button>
<button id="dmxbtn" style="display:none;" type="submit" onclick="window.location=getURL('/settings/dmx')">DMX Output</button>
<button type="submit" onclick="window.location=getURL('/settings/sync')">Sync Interfaces</button>
<button type="submit" onclick="window.location=getURL('/settings/time')">Time & Macros</button>
Expand Down
185 changes: 185 additions & 0 deletions wled00/data/settings_fx.htm
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<!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>Effect Visibility</title>
<style> html { visibility: hidden; } </style>
<style>
.fxcols { display: flex; flex-direction: column; gap: 6px; }
.fxcol { display: flex; flex-direction: column; }
.fxcol h3 { margin: 0 0 4px 0; font-size: 0.95em; display: flex; align-items: center; gap: 6px; }
.fxcol h3 .selbtns { margin-left: auto; display: inline-flex; gap: 4px; }
.fxcol h3 .selbtns button {
font-size: 0.8em; padding: 2px 8px; border-radius: 3px;
background: #444; color: #fff; border: 0; cursor: pointer;
margin: 0; min-width: 0;
}
.fxcol h3 .selbtns button:hover { background: #555; }
.fxlist {
border: 1px solid #555;
background: #111;
min-height: 200px; max-height: 50vh; overflow-y: auto;
padding: 2px; border-radius: 4px;
text-align: left;
}
.fxitem {
padding: 4px 6px; margin: 1px 0; border-radius: 3px;
cursor: pointer; user-select: none;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
font-size: 0.9em;
}
.fxitem:hover { background: #333; }
.fxitem.sel { background: #888; color: #111; }
.fxitem.solid { opacity: 0.55; cursor: not-allowed; font-style: italic; }
.fxbtns { display: flex; flex-direction: row; justify-content: center; gap: 6px; }
.fxbtns button { padding: 6px 10px; margin: 4px 0; min-width: 80px; font-size: 0.95em; }
.counts { font-size: 0.8em; opacity: 0.7; }
</style>
<script>
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S);
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();

var allEffects = []; // [{id,name}], id 0 always first
var hiddenSet = new Set(); // ids currently hidden
var dirty = false;

function S() {
getLoc();
if (loc) d.Sf.action = getURL('/settings/fx');
// ?all=1 returns real names for user-hidden effects so the Hidden list is populated
Promise.all([
fetch(getURL('/json/effects?all=1')).then(r=>r.json()),
fetch(getURL('/json/cfg')).then(r=>r.json())
]).then(([effs, cfg]) => {
allEffects = effs.map((name, id) => ({id, name}));
let h = (cfg && cfg.fx && cfg.fx.hidden) || [];
hiddenSet = new Set(h.filter(id => id !== 0));
render();
d.documentElement.style.visibility='visible';
}).catch(e => {
alert('Load failed: '+e);
d.documentElement.style.visibility='visible';
});
}

// build-disabled or deprecated slot, not user-toggleable
function isReserved(name) { return name && name.indexOf('RSVD') >= 0; }

function render() {
const visList = gId('visList');
const hidList = gId('hidList');
visList.innerHTML = '';
hidList.innerHTML = '';
let visN = 0, hidN = 0;
for (const e of allEffects) {
if (isReserved(e.name)) continue; // never show RSVD slots in either list
const inHidden = hiddenSet.has(e.id);
const li = d.createElement('div');
li.className = 'fxitem';
li.dataset.id = e.id;
li.textContent = e.name;
if (e.id === 0) {
li.classList.add('solid');
li.title = 'Solid cannot be hidden';
} else {
li.onclick = () => toggleSel(li);
}
Comment thread
softhack007 marked this conversation as resolved.
if (inHidden && e.id !== 0) { hidList.appendChild(li); hidN++; }
else { visList.appendChild(li); visN++; }
}
gId('visCount').textContent = visN;
gId('hidCount').textContent = hidN;
updateBtns();
}

function toggleSel(li) {
li.classList.toggle('sel');
updateBtns();
}

function selectAll(listId, sel) {
// skip Solid (non-selectable, has .solid class)
const items = d.querySelectorAll('#'+listId+' .fxitem:not(.solid)');
for (const li of items) li.classList.toggle('sel', sel);
updateBtns();
}

function updateBtns() {
gId('toHideBtn').disabled = !d.querySelectorAll('#visList .sel').length;
gId('toShowBtn').disabled = !d.querySelectorAll('#hidList .sel').length;
}

function moveSel(fromId, hide) {
const sel = d.querySelectorAll('#'+fromId+' .sel');
if (!sel.length) return;
for (const li of sel) {
const id = parseInt(li.dataset.id);
if (id === 0) continue; // safety: Solid never moves
if (hide) hiddenSet.add(id); else hiddenSet.delete(id);
}
dirty = true;
render();
}

function Save() {
gId('FXH').value = Array.from(hiddenSet).sort((a,b)=>a-b).join(',');
dirty = false;
d.Sf.submit();
}

window.addEventListener('beforeunload', e => {
if (dirty) { e.preventDefault(); e.returnValue = ''; }
});
</script>
</head>
<body>
<form id="form_s" name="Sf" method="post">
<div class="toprow">
<div class="helpB"><button type="button" onclick="H('features/effects/#effect-visibility')">?</button></div>
<button type="button" onclick="B()">Back</button><button type="button" onclick="Save()">Save</button><hr>
</div>
<h2>Effect Visibility</h2>
<div class="sec">
<i>Hide effects you don't use so they don't clutter the main UI's effect picker.
Hidden effects are reported as <code>RSVD</code> on the JSON API.</i><br><br>
<i>Presets, playlists, or external API calls that reference a hidden effect will fall
through to the next visible effect — un-hide to restore the original mapping.</i>

<div class="fxcols">
<div class="fxcol">
<h3>Visible <span class="counts">(<span id="visCount">0</span>)</span>
<span class="selbtns">
<button type="button" onclick="selectAll('visList', true)">All</button>
<button type="button" onclick="selectAll('visList', false)">None</button>
</span>
</h3>
<div id="visList" class="fxlist"></div>
</div>
<div class="fxbtns">
<button id="toHideBtn" type="button" onclick="moveSel('visList', true)" disabled>Hide &darr;</button>
<button id="toShowBtn" type="button" onclick="moveSel('hidList', false)" disabled>&uarr; Show</button>
</div>
<div class="fxcol">
<h3>Hidden <span class="counts">(<span id="hidCount">0</span>)</span>
<span class="selbtns">
<button type="button" onclick="selectAll('hidList', true)">All</button>
<button type="button" onclick="selectAll('hidList', false)">None</button>
</span>
</h3>
<div id="hidList" class="fxlist"></div>
</div>
</div>

<input type="hidden" name="FXH" id="FXH" value="">
</div>
<hr><button type="button" onclick="B()">Back</button><button type="button" onclick="Save()">Save</button>
</form>
</body>
</html>
2 changes: 1 addition & 1 deletion wled00/fcn_declare.h
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ bool deserializeState(JsonObject root, byte callMode = CALL_MODE_DIRECT_CHANGE,
void serializeSegment(const JsonObject& root, const Segment& seg, byte id, bool forPreset = false, bool segmentBounds = true);
void serializeState(JsonObject root, bool forPreset = false, bool includeBri = true, bool segmentBounds = true, bool selectedSegmentsOnly = false);
void serializeInfo(JsonObject root);
void serializeModeNames(JsonArray arr);
void serializeModeNames(JsonArray arr, bool bypassHide = false);
void serializePins(JsonObject root);
void serveJson(AsyncWebServerRequest* request);
#ifdef WLED_ENABLE_JSONLIVE
Expand Down
9 changes: 7 additions & 2 deletions wled00/json.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1188,10 +1188,15 @@ void serializePins(JsonObject root)

// deserializes mode names string into JsonArray
// also removes effect data extensions (@...) from deserialised names
void serializeModeNames(JsonArray arr)
// bypassHide=true returns real names for user-hidden effects (used by /settings/fx)
void serializeModeNames(JsonArray arr, bool bypassHide)
{
char lineBuffer[256];
for (size_t i = 0; i < strip.getModeCount(); i++) {
if (!bypassHide && isFxHidden((uint8_t)i)) {
arr.add("RSVD");
continue;
}
strncpy_P(lineBuffer, strip.getModeData(i), sizeof(lineBuffer)/sizeof(char)-1);
lineBuffer[sizeof(lineBuffer)/sizeof(char)-1] = '\0'; // terminate string
if (lineBuffer[0] != 0) {
Expand Down Expand Up @@ -1352,7 +1357,7 @@ void serveJson(AsyncWebServerRequest* request)
case json_target::palettes:
serializePalettes(lDoc, request->hasParam(F("page")) ? request->getParam(F("page"))->value().toInt() : 0); break;
case json_target::effects:
serializeModeNames(lDoc); break;
serializeModeNames(lDoc, request->hasParam(F("all"))); break;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Parse all by value instead of mere presence.

Line 1360 currently enables bypass for any all query key (including all=0). If the contract is all=1, this should validate the value before bypassing hide behavior.

Suggested fix
-      serializeModeNames(lDoc, request->hasParam(F("all"))); break;
+      bool bypassHide = false;
+      if (request->hasParam(F("all"))) {
+        bypassHide = request->getParam(F("all"))->value().toInt() == 1;
+      }
+      serializeModeNames(lDoc, bypassHide); break;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
serializeModeNames(lDoc, request->hasParam(F("all"))); break;
bool bypassHide = false;
if (request->hasParam(F("all"))) {
bypassHide = request->getParam(F("all"))->value().toInt() == 1;
}
serializeModeNames(lDoc, bypassHide); break;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/json.cpp` at line 1360, The code currently calls
serializeModeNames(lDoc, request->hasParam(F("all"))) which treats any presence
of the "all" query key (including "all=0") as true; change this to parse the
parameter value and only bypass hide behavior when it equals the expected truthy
value (e.g., "1" or "true"). Replace the second argument to serializeModeNames
with an expression that fetches the "all" parameter value (via
request->getParam(F("all")) or equivalent), checks it is non-null, and compares
its value to "1" (or case-insensitive "true") so serializeModeNames(lDoc,
/*value check*/). Ensure you reference the same F("all") key and handle
null/missing param safely.

case json_target::networks:
serializeNetworks(lDoc); break;
case json_target::config:
Expand Down
18 changes: 16 additions & 2 deletions wled00/set.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
return;
}

//0: menu 1: wifi 2: leds 3: ui 4: sync 5: time 6: sec 7: DMX 8: usermods 9: N/A 10: 2D
if (subPage < 1 || subPage > 10 || !correctPIN) return;
// see SUBPAGE_* in const.h
if (subPage < 1 || subPage > SUBPAGE_LAST || !correctPIN) return;

//WIFI SETTINGS
if (subPage == SUBPAGE_WIFI)
Expand Down Expand Up @@ -877,6 +877,20 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
}
#endif

if (subPage == SUBPAGE_FX)
{
for (size_t i = 0; i < 8; i++) hiddenFxMask[i] = 0;
String s = request->arg(F("FXH"));
int idx = 0;
while (idx < (int)s.length()) {
int comma = s.indexOf(',', idx);
int end = (comma < 0) ? s.length() : comma;
int id = s.substring(idx, end).toInt();
if (id > 0 && id < 256) setFxHidden((uint8_t)id, true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp hidden effect IDs to active mode range.

Line 889 accepts any ID <256; malformed/stale IDs beyond strip.getModeCount() are still applied. Clamp to current mode count to keep runtime state clean.

Suggested fix
-      if (id > 0 && id < 256) setFxHidden((uint8_t)id, true);
+      if (id > 0 && id < 256 && (size_t)id < strip.getModeCount()) {
+        setFxHidden((uint8_t)id, true);
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/set.cpp` at line 889, The current check allows ids < 256 but still
accepts IDs that exceed the active mode count; update the condition in the code
path that calls setFxHidden((uint8_t)id, true) to also ensure id <=
strip.getModeCount() (or < strip.getModeCount() if modes are zero-indexed) so
only valid active-mode IDs are clamped and passed to setFxHidden; reference the
id variable, setFxHidden(...) call and strip.getModeCount() when applying the
additional bound check.

idx = (comma < 0) ? s.length() : comma + 1;
}
}

lastEditTime = millis();
// do not save if factory reset or LED settings (which are saved after LED re-init)
configNeedsWrite = subPage != SUBPAGE_LEDS && !(subPage == SUBPAGE_SEC && doReboot);
Expand Down
4 changes: 4 additions & 0 deletions wled00/wled.h
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,10 @@ WLED_GLOBAL char serverDescription[33] _INIT(SERVERNAME); // use predefined nam
#endif
WLED_GLOBAL bool simplifiedUI _INIT(false); // enable simplified UI
WLED_GLOBAL byte cacheInvalidate _INIT(0); // used to invalidate browser cache
WLED_GLOBAL uint32_t hiddenFxMask[8] _INIT_N(({0,0,0,0,0,0,0,0})); // bitmap, 1 bit per effect id (0..255); 1 = user-hidden
inline bool isFxHidden(uint8_t id) { return (hiddenFxMask[id >> 5] >> (id & 31)) & 1u; }
inline void setFxHidden(uint8_t id, bool hide) { if (hide) hiddenFxMask[id >> 5] |= (1u << (id & 31));
else hiddenFxMask[id >> 5] &= ~(1u << (id & 31)); }

// Sync CONFIG
WLED_GLOBAL NodesMap Nodes;
Expand Down
3 changes: 3 additions & 0 deletions wled00/wled_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
else if (url.indexOf( "2D") > 0) subPage = SUBPAGE_2D;
#endif
else if (url.indexOf(F("pins")) > 0) subPage = SUBPAGE_PINS;
else if (url.indexOf(F("fx")) > 0) subPage = SUBPAGE_FX;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a more specific URL pattern to avoid false matches.

The pattern url.indexOf(F("fx")) will match any URL containing the substring "fx" (e.g., /settings/prefix would incorrectly route to SUBPAGE_FX). Other subpage checks use longer, more distinctive strings. Consider matching the full string "fx" after the last / or requiring it to be a complete path segment.

🔍 Proposed refinement
-    else if (url.indexOf(F("fx"))   > 0) subPage = SUBPAGE_FX;
+    else if (url.indexOf(F("/fx"))  > 0) subPage = SUBPAGE_FX;

Alternatively, if you want to be even more strict, check for /settings/fx:

-    else if (url.indexOf(F("fx"))   > 0) subPage = SUBPAGE_FX;
+    else if (url.indexOf(F("settings/fx")) >= 0) subPage = SUBPAGE_FX;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
else if (url.indexOf(F("fx")) > 0) subPage = SUBPAGE_FX;
else if (url.indexOf(F("/fx")) > 0) subPage = SUBPAGE_FX;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/wled_server.cpp` at line 755, The current check uses
url.indexOf(F("fx")) which can match "fx" anywhere; change the logic that
assigns subPage = SUBPAGE_FX to only match a full path segment "fx" (e.g., test
that the segment after the last '/' equals F("fx") or that the URL ends with
F("/fx") or matches F("/settings/fx")). Update the conditional around the url
variable that sets subPage to SUBPAGE_FX so it extracts the last path segment
(or uses an endsWith-style check) instead of indexOf, ensuring other routes
containing "fx" do not falsely match.

else if (url.indexOf(F("lock")) > 0) subPage = SUBPAGE_LOCK;
}
else if (url.indexOf("/update") >= 0) subPage = SUBPAGE_UPDATE; // update page, for PIN check
Expand Down Expand Up @@ -796,6 +797,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
case SUBPAGE_2D : strcpy_P(s, PSTR("2D")); break;
#endif
case SUBPAGE_PINREQ : strcpy_P(s, correctPIN ? PSTR("PIN accepted") : PSTR("PIN rejected")); break;
case SUBPAGE_FX : strcpy_P(s, PSTR("Effect Visibility")); break;
}

if (subPage != SUBPAGE_PINREQ) strcat_P(s, PSTR(" settings saved."));
Expand Down Expand Up @@ -846,6 +848,7 @@ void serveSettings(AsyncWebServerRequest* request, bool post) {
case SUBPAGE_2D : content = PAGE_settings_2D; len = PAGE_settings_2D_length; break;
#endif
case SUBPAGE_PINS : content = PAGE_settings_pininfo; len = PAGE_settings_pininfo_length; break;
case SUBPAGE_FX : content = PAGE_settings_fx; len = PAGE_settings_fx_length; break;
case SUBPAGE_LOCK : {
correctPIN = !strlen(settingsPIN); // lock if a pin is set
serveMessage(request, 200, strlen(settingsPIN) > 0 ? PSTR("Settings locked") : PSTR("No PIN set"), FPSTR(s_redirecting), 1);
Expand Down
Loading