diff --git a/.gitignore b/.gitignore index ec9d4efcc3..a3c1bfbdcc 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ wled-update.sh /wled00/Release /wled00/wled00.ino.cpp /wled00/html_*.h +/wled00/js_*.h diff --git a/tools/cdata.js b/tools/cdata.js index c05b28e522..75661de129 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -26,7 +26,7 @@ const packageJson = require("../package.json"); // Export functions for testing module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan }; -const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_pxmagic.h", "wled00/html_settings.h", "wled00/html_other.h"] +const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_pxmagic.h", "wled00/html_settings.h", "wled00/html_other.h", "wled00/js_iro.h"] // \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset const wledBanner = ` @@ -247,6 +247,20 @@ writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixar //writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal'); writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic'); +writeChunks( + "wled00/data/", + [ + { + file: "iro.js", + name: "JS_iro", + method: "gzip", + filter: "plain", // no minification, it is already minified + mangle: (s) => s.replace(/^\/\*![\s\S]*?\*\//, '') // remove license comment at the top + } + ], + "wled00/js_iro.h" +); + writeChunks( "wled00/data/cpal", [ diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index b41517258f..a2e1138ca2 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -1732,14 +1732,14 @@ class AudioReactive : public Usermod { } #endif } - if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { + if (palettes > 0 && root.containsKey(F("rmcpal"))) { // handle removal of custom palettes from JSON call so we don't break things removeAudioPalettes(); } } void onStateChange(uint8_t callMode) override { - if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()<10) { + if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size() pDoc; // barely enough to fit 72 numbers -> TODO: current format uses 214 bytes max per palette, why is this buffer so large? + unsigned emptyPaletteGap = 0; // count gaps in palette files to stop looking for more (each exists() call takes ~5ms) for (int index = 0; index < WLED_MAX_CUSTOM_PALETTES; index++) { char fileName[32]; sprintf_P(fileName, PSTR("/palette%d.json"), index); - - StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers if (WLED_FS.exists(fileName)) { + emptyPaletteGap = 0; // reset gap counter if file exists DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName); if (readObjectFromFile(fileName, nullptr, &pDoc)) { JsonArray pal = pDoc[F("palette")]; @@ -288,7 +289,8 @@ void loadCustomPalettes() { } } } else { - break; + emptyPaletteGap++; + if (emptyPaletteGap > WLED_MAX_CUSTOM_PALETTE_GAP) break; // stop looking for more palettes } } } diff --git a/wled00/const.h b/wled00/const.h index 8891dfcaee..c9a5fadcc0 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -15,6 +15,7 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C #else #define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10 #endif +#define WLED_MAX_CUSTOM_PALETTE_GAP 20 // max number of empty palette files in a row before stopping to look for more (20 takes 100ms) // You can define custom product info from build flags. // This is useful to allow API consumer to identify what type of WLED version diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index b8e0e08be1..6711d31108 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -1,646 +1,908 @@ - - - - - - WLED Custom Palette Editor - - - - - + + + WLED Palette Editor + + -
-
-

- - - - - - - WLED Palette Editor -

-
- -
-
-
-
- -
Custom palettes
-
-
- -
-
Click gradient to add. Box = color. Red = delete. Arrow = upload. Pencil = edit.
-
-
-
-
Static palettes
-
-
+
+

WLED Palette Editor

+ +
+
+
+ + + +
+
+ +
+
+ + +
+
+ + + +
+
+ +
+
+
+ + + +
+ +
+ Warning: Adding many custom palettes might cause stability issues, create backups +
+
+ +
+ +
+ +
+
+ +
+ +
by @dedehai
+
+ + + - - - + \ No newline at end of file diff --git a/wled00/data/index.htm b/wled00/data/index.htm index 22f1987e93..361a18c1c7 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -10,7 +10,7 @@ WLED - +
Loading WLED UI...
@@ -129,8 +129,7 @@
- - +

Color palette

@@ -364,8 +363,9 @@ - + diff --git a/wled00/data/index.js b/wled00/data/index.js index 2514f03fb1..13c0c48207 100644 --- a/wled00/data/index.js +++ b/wled00/data/index.js @@ -8,6 +8,7 @@ var segLmax = 0; // size (in pixels) of largest selected segment var selectedFx = 0; var selectedPal = 0; var csel = 0; // selected color slot (0-2) +var cpick; // iro color picker var currentPreset = -1; var lastUpdate = 0; var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0; @@ -43,16 +44,24 @@ var hol = [ [0, 0, 1, 1, "https://images.alphacoders.com/119/1198800.jpg"] // new year ]; -var cpick = new iro.ColorPicker("#picker", { - width: 260, - wheelLightness: false, - wheelAngle: 270, - wheelDirection: "clockwise", - layout: [{ - component: iro.ui.Wheel, - options: {} - }] -}); +// load iro.js sequentially to avoid 503 errors, retries until successful +(function loadIro() { + const l = d.createElement('script'); + l.src = 'iro.js'; + l.onload = () => { + cpick = new iro.ColorPicker("#picker", { + width: 260, + wheelLightness: false, + wheelAngle: 270, + wheelDirection: "clockwise", + layout: [{component: iro.ui.Wheel, options: {}}] + }); + d.readyState === 'complete' ? onLoad() : window.addEventListener('load', onLoad); + }; + l.onerror = () => setTimeout(loadIro, 100); + document.head.appendChild(l); +})(); + function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();} function sCol(na, col) {d.documentElement.style.setProperty(na, col);} @@ -1011,8 +1020,6 @@ function populatePalettes() ); } } - if (li.cpalcount>0) gId("rmPal").classList.remove("hide"); - else gId("rmPal").classList.add("hide"); } function redrawPalPrev() @@ -1683,14 +1690,12 @@ function setEffectParameters(idx) paOnOff[0] = paOnOff[0].substring(0,dPos); } if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0]; - gId("adPal").classList.remove("hide"); - if (lastinfo.cpalcount>0) gId("rmPal").classList.remove("hide"); + gId("editPal").classList.remove("hide"); } else { // disable palette list text += ' not used'; palw.style.display = "none"; - gId("adPal").classList.add("hide"); - gId("rmPal").classList.add("hide"); + gId("editPal").classList.add("hide"); // Close palette dialog if not available if (palw.lastElementChild.tagName == "DIALOG") { palw.lastElementChild.close(); diff --git a/wled00/json.cpp b/wled00/json.cpp index d2b771c590..db8db6f832 100644 --- a/wled00/json.cpp +++ b/wled00/json.cpp @@ -528,17 +528,15 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId) else callMode = CALL_MODE_DIRECT_CHANGE; // possible bugfix for playlist only containing HTTP API preset FX=~ } - if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as()) { - if (customPalettes.size()) { - char fileName[32]; - sprintf_P(fileName, PSTR("/palette%d.json"), customPalettes.size()-1); - if (WLED_FS.exists(fileName)) WLED_FS.remove(fileName); - loadCustomPalettes(); - } + if (root.containsKey(F("rmcpal"))) { + char fileName[32]; + sprintf_P(fileName, PSTR("/palette%d.json"), root[F("rmcpal")].as()); + if (WLED_FS.exists(fileName)) WLED_FS.remove(fileName); + loadCustomPalettes(); } doAdvancePlaylist = root[F("np")] | doAdvancePlaylist; //advances to next preset in playlist when true - + JsonObject wifi = root[F("wifi")]; if (!wifi.isNull()) { bool apMode = getBoolVal(wifi[F("ap")], apActive); diff --git a/wled00/wled_server.cpp b/wled00/wled_server.cpp index 75b4ae3f5a..e252244f35 100644 --- a/wled00/wled_server.cpp +++ b/wled00/wled_server.cpp @@ -10,6 +10,7 @@ #include "html_ui.h" #include "html_settings.h" #include "html_other.h" +#include "js_iro.h" #ifdef WLED_ENABLE_PIXART #include "html_pixart.h" #endif @@ -27,6 +28,7 @@ static const char s_rebooting [] PROGMEM = "Rebooting now..."; static const char s_notimplemented[] PROGMEM = "Not implemented"; static const char s_accessdenied[] PROGMEM = "Access Denied"; static const char _common_js[] PROGMEM = "/common.js"; +static const char _iro_js[] PROGMEM = "/iro.js"; //Is this an IP? static bool isIp(const String &str) { @@ -266,6 +268,10 @@ void initServer() handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length); }); + server.on(_iro_js, HTTP_GET, [](AsyncWebServerRequest *request) { + handleStaticContent(request, FPSTR(_iro_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_iro, JS_iro_length); + }); + //settings page server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){ serveSettings(request);