New custom palettes editor (#5010)
* full refactoring, added live preview, better minifying in cdata.js * update main UI buttons, support for gaps in cpal files, cpal UI cleanup * fixed some layout issues, added un-ordered cpal deletion * changed to tab indentation, paste button border color now holds stored color * fix preview to work properly and some other fixes in UI * always unfreeze * new approach to loading iro.js, add harmonic random palette, many fixes. * decoupling iro.j, update UI of cpal.htm - load iro.js sequentially - no parallel requests in cpal.htm - update UI buttons - fix showing sequential loading of palettes (using opacity) - better UX for mobile (larger markers, larger editor) - various fixes * small change to buttons * load iro.js dynamically, remove iro.js from index.htm, revert changes to cdata.js * improved visibility for very dark/black palettes and markers
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -25,3 +25,4 @@ wled-update.sh
|
|||||||
/wled00/Release
|
/wled00/Release
|
||||||
/wled00/wled00.ino.cpp
|
/wled00/wled00.ino.cpp
|
||||||
/wled00/html_*.h
|
/wled00/html_*.h
|
||||||
|
/wled00/js_*.h
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ const packageJson = require("../package.json");
|
|||||||
// Export functions for testing
|
// Export functions for testing
|
||||||
module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan };
|
module.exports = { isFileNewerThan, isAnyFileInFolderNewerThan };
|
||||||
|
|
||||||
const output = ["wled00/html_ui.h", "wled00/html_pixart.h", "wled00/html_cpal.h", "wled00/html_edit.h", "wled00/html_pxmagic.h", "wled00/html_pixelforge.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_edit.h", "wled00/html_pxmagic.h", "wled00/html_pixelforge.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
|
// \x1b[34m is blue, \x1b[36m is cyan, \x1b[0m is reset
|
||||||
const wledBanner = `
|
const wledBanner = `
|
||||||
@@ -257,6 +257,19 @@ writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'px
|
|||||||
writeHtmlGzipped("wled00/data/pixelforge/pixelforge.htm", "wled00/html_pixelforge.h", 'pixelforge', false); // do not inline css
|
writeHtmlGzipped("wled00/data/pixelforge/pixelforge.htm", "wled00/html_pixelforge.h", 'pixelforge', false); // do not inline css
|
||||||
//writeHtmlGzipped("wled00/data/edit.htm", "wled00/html_edit.h", 'edit');
|
//writeHtmlGzipped("wled00/data/edit.htm", "wled00/html_edit.h", 'edit');
|
||||||
|
|
||||||
|
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(
|
writeChunks(
|
||||||
"wled00/data",
|
"wled00/data",
|
||||||
|
|||||||
@@ -1742,14 +1742,14 @@ class AudioReactive : public Usermod {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
if (root.containsKey(F("rmcpal")) && root[F("rmcpal")].as<bool>()) {
|
if (palettes > 0 && root.containsKey(F("rmcpal"))) {
|
||||||
// handle removal of custom palettes from JSON call so we don't break things
|
// handle removal of custom palettes from JSON call so we don't break things
|
||||||
removeAudioPalettes();
|
removeAudioPalettes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onStateChange(uint8_t callMode) override {
|
void onStateChange(uint8_t callMode) override {
|
||||||
if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()<10) {
|
if (initDone && enabled && addPalettes && palettes==0 && customPalettes.size()<WLED_MAX_CUSTOM_PALETTES) {
|
||||||
// if palettes were removed during JSON call re-add them
|
// if palettes were removed during JSON call re-add them
|
||||||
createAudioPalettes();
|
createAudioPalettes();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -249,12 +249,13 @@ void loadCustomPalettes() {
|
|||||||
byte tcp[72]; //support gradient palettes with up to 18 entries
|
byte tcp[72]; //support gradient palettes with up to 18 entries
|
||||||
CRGBPalette16 targetPalette;
|
CRGBPalette16 targetPalette;
|
||||||
customPalettes.clear(); // start fresh
|
customPalettes.clear(); // start fresh
|
||||||
|
StaticJsonDocument<1536> 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++) {
|
for (int index = 0; index < WLED_MAX_CUSTOM_PALETTES; index++) {
|
||||||
char fileName[32];
|
char fileName[32];
|
||||||
sprintf_P(fileName, PSTR("/palette%d.json"), index);
|
sprintf_P(fileName, PSTR("/palette%d.json"), index);
|
||||||
|
|
||||||
StaticJsonDocument<1536> pDoc; // barely enough to fit 72 numbers
|
|
||||||
if (WLED_FS.exists(fileName)) {
|
if (WLED_FS.exists(fileName)) {
|
||||||
|
emptyPaletteGap = 0; // reset gap counter if file exists
|
||||||
DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName);
|
DEBUGFX_PRINTF_P(PSTR("Reading palette from %s\n"), fileName);
|
||||||
if (readObjectFromFile(fileName, nullptr, &pDoc)) {
|
if (readObjectFromFile(fileName, nullptr, &pDoc)) {
|
||||||
JsonArray pal = pDoc[F("palette")];
|
JsonArray pal = pDoc[F("palette")];
|
||||||
@@ -288,7 +289,8 @@ void loadCustomPalettes() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break;
|
emptyPaletteGap++;
|
||||||
|
if (emptyPaletteGap > WLED_MAX_CUSTOM_PALETTE_GAP) break; // stop looking for more palettes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_C
|
|||||||
#else
|
#else
|
||||||
#define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10
|
#define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10
|
||||||
#endif
|
#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.
|
// You can define custom product info from build flags.
|
||||||
// This is useful to allow API consumer to identify what type of WLED version
|
// This is useful to allow API consumer to identify what type of WLED version
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@
|
|||||||
<title>WLED</title>
|
<title>WLED</title>
|
||||||
<link rel="stylesheet" href="index.css">
|
<link rel="stylesheet" href="index.css">
|
||||||
</head>
|
</head>
|
||||||
<body onload="onLoad()">
|
<body>
|
||||||
|
|
||||||
<div id="cv" class="overlay">Loading WLED UI...</div>
|
<div id="cv" class="overlay">Loading WLED UI...</div>
|
||||||
<noscript><div class="overlay" style="opacity:1;">Sorry, WLED UI needs JavaScript!</div></noscript>
|
<noscript><div class="overlay" style="opacity:1;">Sorry, WLED UI needs JavaScript!</div></noscript>
|
||||||
@@ -129,8 +129,7 @@
|
|||||||
<div style="padding: 8px 0;" id="btns">
|
<div style="padding: 8px 0;" id="btns">
|
||||||
<button class="btn btn-xs" title="File editor" type="button" id="edit" onclick="window.location.href=getURL('/edit')"><i class="icons btn-icon"></i></button>
|
<button class="btn btn-xs" title="File editor" type="button" id="edit" onclick="window.location.href=getURL('/edit')"><i class="icons btn-icon"></i></button>
|
||||||
<button class="btn btn-xs" title="PixelForge" type="button" onclick="window.location.href=getURL('/pixelforge.htm')"><i class="icons btn-icon"></i></button>
|
<button class="btn btn-xs" title="PixelForge" type="button" onclick="window.location.href=getURL('/pixelforge.htm')"><i class="icons btn-icon"></i></button>
|
||||||
<button class="btn btn-xs" title="Add custom palette" type="button" id="adPal" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon"></i></button>
|
<button class="btn btn-xs" title="Custom palettes" type="button" id="editPal" onclick="window.location.href=getURL('/cpal.htm')"><i class="icons btn-icon"></i></button>
|
||||||
<button class="btn btn-xs" title="Remove last custom palette" type="button" id="rmPal" onclick="palettesData=null;localStorage.removeItem('wledPalx');requestJson({rmcpal:true});setTimeout(loadPalettes,250,loadPalettesData);"><i class="icons btn-icon"></i></button>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="labels hd" id="pall"><i class="icons sel-icon" onclick="tglHex()"></i> Color palette</p>
|
<p class="labels hd" id="pall"><i class="icons sel-icon" onclick="tglHex()"></i> Color palette</p>
|
||||||
<div id="palw" class="il">
|
<div id="palw" class="il">
|
||||||
@@ -364,8 +363,9 @@
|
|||||||
<!--
|
<!--
|
||||||
If you want to load iro.js and rangetouch.js as consecutive requests, you can do it like it was done in 0.14.0:
|
If you want to load iro.js and rangetouch.js as consecutive requests, you can do it like it was done in 0.14.0:
|
||||||
https://github.com/wled/WLED/blob/v0.14.0/wled00/data/index.htm
|
https://github.com/wled/WLED/blob/v0.14.0/wled00/data/index.htm
|
||||||
|
A more compact approach is implemented to load iro.js at the beginning of index.js.
|
||||||
-->
|
-->
|
||||||
<script src="iro.js"></script>
|
<!-- <script src="iro.js"></script> NOTE: iro.js is loaded at the beginning of index.js -->
|
||||||
<script src="rangetouch.js"></script>
|
<script src="rangetouch.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ var segLmax = 0; // size (in pixels) of largest selected segment
|
|||||||
var selectedFx = 0;
|
var selectedFx = 0;
|
||||||
var selectedPal = 0;
|
var selectedPal = 0;
|
||||||
var csel = 0; // selected color slot (0-2)
|
var csel = 0; // selected color slot (0-2)
|
||||||
|
var cpick; // iro color picker
|
||||||
var currentPreset = -1;
|
var currentPreset = -1;
|
||||||
var lastUpdate = 0;
|
var lastUpdate = 0;
|
||||||
var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0;
|
var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0;
|
||||||
@@ -42,16 +43,24 @@ var hol = [
|
|||||||
[0, 0, 1, 1, "https://images.alphacoders.com/119/1198800.jpg"] // new year
|
[0, 0, 1, 1, "https://images.alphacoders.com/119/1198800.jpg"] // new year
|
||||||
];
|
];
|
||||||
|
|
||||||
var cpick = new iro.ColorPicker("#picker", {
|
// 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,
|
width: 260,
|
||||||
wheelLightness: false,
|
wheelLightness: false,
|
||||||
wheelAngle: 270,
|
wheelAngle: 270,
|
||||||
wheelDirection: "clockwise",
|
wheelDirection: "clockwise",
|
||||||
layout: [{
|
layout: [{component: iro.ui.Wheel, options: {}}]
|
||||||
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 handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();}
|
||||||
function sCol(na, col) {d.documentElement.style.setProperty(na, col);}
|
function sCol(na, col) {d.documentElement.style.setProperty(na, col);}
|
||||||
@@ -972,8 +981,6 @@ function populatePalettes()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (li.cpalcount>0) gId("rmPal").classList.remove("hide");
|
|
||||||
else gId("rmPal").classList.add("hide");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function redrawPalPrev()
|
function redrawPalPrev()
|
||||||
@@ -1645,14 +1652,12 @@ function setEffectParameters(idx)
|
|||||||
paOnOff[0] = paOnOff[0].substring(0,dPos);
|
paOnOff[0] = paOnOff[0].substring(0,dPos);
|
||||||
}
|
}
|
||||||
if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0];
|
if (paOnOff.length>0 && paOnOff[0] != "!") text = paOnOff[0];
|
||||||
gId("adPal").classList.remove("hide");
|
gId("editPal").classList.remove("hide");
|
||||||
if (lastinfo.cpalcount>0) gId("rmPal").classList.remove("hide");
|
|
||||||
} else {
|
} else {
|
||||||
// disable palette list
|
// disable palette list
|
||||||
text += ' not used';
|
text += ' not used';
|
||||||
palw.style.display = "none";
|
palw.style.display = "none";
|
||||||
gId("adPal").classList.add("hide");
|
gId("editPal").classList.add("hide");
|
||||||
gId("rmPal").classList.add("hide");
|
|
||||||
// Close palette dialog if not available
|
// Close palette dialog if not available
|
||||||
if (palw.lastElementChild.tagName == "DIALOG") {
|
if (palw.lastElementChild.tagName == "DIALOG") {
|
||||||
palw.lastElementChild.close();
|
palw.lastElementChild.close();
|
||||||
|
|||||||
@@ -535,14 +535,12 @@ bool deserializeState(JsonObject root, byte callMode, byte presetId)
|
|||||||
else callMode = CALL_MODE_DIRECT_CHANGE; // possible bugfix for playlist only containing HTTP API preset FX=~
|
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<bool>()) {
|
if (root.containsKey(F("rmcpal"))) {
|
||||||
if (customPalettes.size()) {
|
|
||||||
char fileName[32];
|
char fileName[32];
|
||||||
sprintf_P(fileName, PSTR("/palette%d.json"), customPalettes.size()-1);
|
sprintf_P(fileName, PSTR("/palette%d.json"), root[F("rmcpal")].as<uint8_t>());
|
||||||
if (WLED_FS.exists(fileName)) WLED_FS.remove(fileName);
|
if (WLED_FS.exists(fileName)) WLED_FS.remove(fileName);
|
||||||
loadCustomPalettes();
|
loadCustomPalettes();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
doAdvancePlaylist = root[F("np")] | doAdvancePlaylist; //advances to next preset in playlist when true
|
doAdvancePlaylist = root[F("np")] | doAdvancePlaylist; //advances to next preset in playlist when true
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "html_ui.h"
|
#include "html_ui.h"
|
||||||
#include "html_settings.h"
|
#include "html_settings.h"
|
||||||
#include "html_other.h"
|
#include "html_other.h"
|
||||||
|
#include "js_iro.h"
|
||||||
#ifdef WLED_ENABLE_PIXART
|
#ifdef WLED_ENABLE_PIXART
|
||||||
#include "html_pixart.h"
|
#include "html_pixart.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -36,6 +37,7 @@ static const char s_cache_control[] PROGMEM = "Cache-Control";
|
|||||||
static const char s_no_store[] PROGMEM = "no-store";
|
static const char s_no_store[] PROGMEM = "no-store";
|
||||||
static const char s_expires[] PROGMEM = "Expires";
|
static const char s_expires[] PROGMEM = "Expires";
|
||||||
static const char _common_js[] PROGMEM = "/common.js";
|
static const char _common_js[] PROGMEM = "/common.js";
|
||||||
|
static const char _iro_js[] PROGMEM = "/iro.js";
|
||||||
|
|
||||||
|
|
||||||
//Is this an IP?
|
//Is this an IP?
|
||||||
@@ -350,6 +352,10 @@ void initServer()
|
|||||||
handleStaticContent(request, FPSTR(_common_js), 200, FPSTR(CONTENT_TYPE_JAVASCRIPT), JS_common, JS_common_length);
|
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
|
//settings page
|
||||||
server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on(F("/settings"), HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
serveSettings(request);
|
serveSettings(request);
|
||||||
|
|||||||
Reference in New Issue
Block a user