"unrestricted" number of custom palettes (#4932)

- allow more than 10 custom palettes
- move palettes into CPP file
- Fix for minimizing cpal.htm (saves 2k of flash)
- shortened names in cpal, saves about 400 bytes of Flash after packing
- removed async from common.js loading to prevent errors on page loads if the file is not cached
- restricted nubmer of user palettes on ESP8266 to 10
- unrestricted number of user palettes on all other platforms (total max palettes: 256)
- added a warning when adding more than 10 palettes to let the user decide to risk it
- Bugfixes in palette enumeration, fixed AR palette adding
- AR palettes are now also added if there are more than 10 custom palettes

Co-authored-by: Blaž Kristan <blaz@kristan-sp.si>
This commit is contained in:
Damian Schneider
2025-09-22 20:09:54 +02:00
committed by GitHub
parent 4b1b0fe045
commit 529edfc39b
24 changed files with 389 additions and 469 deletions

View File

@@ -143,7 +143,7 @@ async function writeHtmlGzipped(sourceFile, resultFile, page) {
console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes"); console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes");
const array = hexdump(result); const array = hexdump(result);
let src = singleHeader; let src = singleHeader;
src += `const uint16_t PAGE_${page}_L = ${result.length};\n`; src += `const uint16_t PAGE_${page}_length = ${result.length};\n`;
src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`; src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`;
console.info("Writing " + resultFile); console.info("Writing " + resultFile);
fs.writeFileSync(resultFile, src); fs.writeFileSync(resultFile, src);
@@ -244,9 +244,22 @@ if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.ar
writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index'); writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index');
writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart'); writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart');
writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal'); //writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal');
writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic'); writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic');
writeChunks(
"wled00/data/cpal",
[
{
file: "cpal.htm",
name: "PAGE_cpal",
method: "gzip",
filter: "html-minify"
}
],
"wled00/html_cpal.h"
);
writeChunks( writeChunks(
"wled00/data", "wled00/data",
[ [

View File

@@ -1981,7 +1981,7 @@ void AudioReactive::createAudioPalettes(void) {
if (palettes) return; if (palettes) return;
DEBUG_PRINTLN(F("Adding audio palettes.")); DEBUG_PRINTLN(F("Adding audio palettes."));
for (int i=0; i<MAX_PALETTES; i++) for (int i=0; i<MAX_PALETTES; i++)
if (customPalettes.size() < 10) { if (customPalettes.size() < WLED_MAX_CUSTOM_PALETTES) {
customPalettes.push_back(CRGBPalette16(CRGB(BLACK))); customPalettes.push_back(CRGBPalette16(CRGB(BLACK)));
palettes++; palettes++;
DEBUG_PRINTLN(palettes); DEBUG_PRINTLN(palettes);

View File

@@ -8,7 +8,6 @@
Parts of the code adapted from WLED Sound Reactive Parts of the code adapted from WLED Sound Reactive
*/ */
#include "wled.h" #include "wled.h"
#include "palettes.h"
// setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels // setUpMatrix() - constructs ledmap array from matrix of panels with WxH pixels
// this converts physical (possibly irregular) LED arrangement into well defined // this converts physical (possibly irregular) LED arrangement into well defined

View File

@@ -11,7 +11,6 @@
*/ */
#include "wled.h" #include "wled.h"
#include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h? #include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h?
#include "palettes.h"
/* /*
Custom per-LED mapping has moved! Custom per-LED mapping has moved!
@@ -226,8 +225,12 @@ void Segment::resetIfRequired() {
} }
CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // there is one randomy generated palette (1) followed by 4 palettes created from segment colors (2-5)
if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; // those are followed by 7 fastled palettes (6-12) and 59 gradient palettes (13-71)
// then come the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette)
// palette 0 is a varying palette depending on effect and may be replaced by segment's color if so
// instructed in color_from_palette()
if (pal > FIXED_PALETTE_COUNT && pal <= 255-customPalettes.size()) pal = 0; // out of bounds palette
//default palette. Differs depending on effect //default palette. Differs depending on effect
if (pal == 0) pal = _default_palette; // _default_palette is set in setMode() if (pal == 0) pal = _default_palette; // _default_palette is set in setMode()
switch (pal) { switch (pal) {
@@ -263,13 +266,13 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
} }
break;} break;}
default: //progmem palettes default: //progmem palettes
if (pal>245) { if (pal > 255 - customPalettes.size()) {
targetPalette = customPalettes[255-pal]; // we checked bounds above targetPalette = customPalettes[255-pal]; // we checked bounds above
} else if (pal < 13) { // palette 6 - 12, fastled palettes } else if (pal < DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT+1) { // palette 6 - 12, fastled palettes
targetPalette = *fastledPalettes[pal-6]; targetPalette = *fastledPalettes[pal-DYNAMIC_PALETTE_COUNT-1];
} else { } else {
byte tcp[72]; byte tcp[72];
memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-(DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT)-1])), 72);
targetPalette.loadDynamicGradientPalette(tcp); targetPalette.loadDynamicGradientPalette(tcp);
} }
break; break;
@@ -573,8 +576,7 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) {
} }
Segment &Segment::setPalette(uint8_t pal) { Segment &Segment::setPalette(uint8_t pal) {
if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette
if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; // custom palettes
if (pal != palette) { if (pal != palette) {
//DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal);
startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment) startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment)

View File

@@ -252,7 +252,7 @@ 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
for (int index = 0; index<10; 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);

View File

@@ -123,7 +123,7 @@ CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette);
CRGBPalette16 generateRandomPalette(); CRGBPalette16 generateRandomPalette();
void loadCustomPalettes(); void loadCustomPalettes();
extern std::vector<CRGBPalette16> customPalettes; extern std::vector<CRGBPalette16> customPalettes;
inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); }
inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); }
void hsv2rgb(const CHSV32& hsv, uint32_t& rgb); void hsv2rgb(const CHSV32& hsv, uint32_t& rgb);
void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); void colorHStoRGB(uint16_t hue, byte sat, byte* rgb);
@@ -141,4 +141,8 @@ void setRandomColor(byte* rgb);
[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false); [[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false);
// palettes
extern const TProgmemRGBPalette16* const fastledPalettes[];
extern const uint8_t* const gGradientPalettes[];
#endif #endif

View File

@@ -6,7 +6,15 @@
* Readability defines and their associated numerical values + compile-time constants * Readability defines and their associated numerical values + compile-time constants
*/ */
#define GRADIENT_PALETTE_COUNT 59 constexpr size_t FASTLED_PALETTE_COUNT = 7; // = sizeof(fastledPalettes) / sizeof(fastledPalettes[0]);
constexpr size_t GRADIENT_PALETTE_COUNT = 59; // = sizeof(gGradientPalettes) / sizeof(gGradientPalettes[0]);
constexpr size_t DYNAMIC_PALETTE_COUNT = 5; // 1-5 are dynamic palettes (1=random,2=primary,3=primary+secondary,4=primary+secondary+tertiary,5=primary+secondary(+tertiary if not black)
constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT + GRADIENT_PALETTE_COUNT; // total number of fixed palettes
#ifndef ESP8266
#define WLED_MAX_CUSTOM_PALETTES (255 - FIXED_PALETTE_COUNT) // allow up to 255 total palettes, user is warned about stability issues when adding more than 10
#else
#define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10
#endif
// 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

View File

@@ -11,6 +11,7 @@
function gId(e) {return d.getElementById(e);} function gId(e) {return d.getElementById(e);}
function cE(e) {return d.createElement(e);} function cE(e) {return d.createElement(e);}
</script> </script>
<script src="common.js" type="text/javascript"></script>
<style> <style>
body { body {
@@ -21,39 +22,39 @@
margin: 0 10px; margin: 0 10px;
line-height: 0.5; line-height: 0.5;
} }
#parent-container { #pCont {
position: relative; position: relative;
width: 100%; width: 100%;
height: 20px; height: 20px;
} }
#bottomContainer { #bCont {
position: absolute; position: absolute;
margin-top: 50px; margin-top: 50px;
} }
#gradient-box { #gBox {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.color-marker, .color-picker-marker { .cMark, .cPickMark {
position: absolute; position: absolute;
border-radius: 3px; border-radius: 3px;
background-color: rgb(192, 192, 192); background-color: rgb(192, 192, 192);
border: 2px solid rgba(68, 68, 68, 0.5); border: 2px solid rgba(68, 68, 68, 0.5);
z-index: 2; z-index: 2;
} }
.color-marker { .cMark {
height: 30px; height: 30px;
width: 7px; width: 7px;
top: 50%; top: 50%;
transform: translateY(-50%); transform: translateY(-50%);
touch-action: none; touch-action: none;
} }
.color-picker-marker { .cPickMark {
height: 7px; height: 7px;
width: 7px; width: 7px;
top: 150%; top: 150%;
} }
.delete-marker { .dMark {
position: absolute; position: absolute;
height: 5px; height: 5px;
width: 5px; width: 5px;
@@ -63,7 +64,7 @@
top: 220%; top: 220%;
z-index: 2; z-index: 2;
} }
.color-picker { .cPick {
position: absolute; position: absolute;
height: 1px; height: 1px;
width: 1px; width: 1px;
@@ -73,13 +74,13 @@
border-color: #111; border-color: #111;
background-color: #111; background-color: #111;
} }
.buttonclass { .btnCls {
padding: 0; padding: 0;
margin: 0; margin: 0;
vertical-align: bottom; vertical-align: bottom;
background-color: #111; background-color: #111;
} }
#bottomContainer span { #bCont span {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
color: #fff; color: #fff;
@@ -87,7 +88,6 @@
vertical-align: middle; vertical-align: middle;
} }
#info { #info {
display: "";
text-align: center; text-align: center;
color: #fff; color: #fff;
font-size: 12px; font-size: 12px;
@@ -104,119 +104,76 @@
width: 800px; width: 800px;
} }
} }
.palette { .pal {height: 20px;}
height: 20px; .pGrads {flex: 1; height: 20px; border-radius: 3px;}
} .pMain {margin-top: 50px; width: 100%;}
.paletteGradients { .pTop {height: fit-content; text-align: center; color: #fff; font-size: 14px; line-height: 1;}
flex: 1; .pGradPar {display: flex; align-items: center; height: fit-content; margin-top: 10px; text-align: center; color: #fff; font-size: 12px; line-height: 1;}
height: 20px; .btnsDiv {display: inline-flex; margin-left: 5px; width: 50px;}
border-radius: 3px; .sSpan, .eSpan {cursor: pointer;}
} h1 {font-size: 1.6rem;}
.palettesMain {
margin-top: 50px;
width: 100%;
}
.palTop {
height: fit-content;
text-align: center;
color: #fff;
font-size: 12px;
position: relative;
line-height: 1;
}
.palGradientParent {
display: flex;
align-items: center;
height: fit-content;
margin-top: 10px;
text-align: center;
color: #fff;
font-size: 12px;
position: relative;
line-height: 1;
}
.buttonsDiv {
display: inline-flex;
margin-left: 5px;
width: 50px;
}
.sendSpan, .editSpan{
cursor: pointer;
}
h1 {
font-size: 1.6rem;
}
</style> </style>
</head> </head>
<body> <body>
<div id="wrap" class="wrap"> <div id="wrap" class="wrap">
<div style="display: flex; justify-content: center;"> <div style="display: flex; justify-content: center;">
<h1 style="display: flex; align-items: center;"> <h1 style="display: flex; align-items: center;">
<svg style="width:36px;height:36px;margin-right:6px;" viewBox="0 0 32 32"> <svg style="width: 36px; height: 36px; margin-right: 6px;" viewBox="0 0 32 32">
<rect style="fill:#003FFF" x="6" y="22" width="8" height="4"/> <rect style="fill: #03F" x="6" y="22" width="8" height="4"/>
<rect style="fill:#003FFF" x="14" y="14" width="4" height="8"/> <rect style="fill: #03F" x="14" y="14" width="4" height="8"/>
<rect style="fill:#003FFF" x="18" y="10" width="4" height="8"/> <rect style="fill: #03F" x="18" y="10" width="4" height="8"/>
<rect style="fill:#003FFF" x="22" y="6" width="8" height="4"/> <rect style="fill: #03F" x="22" y="6" width="8" height="4"/>
</svg> </svg>
<span id="head">WLED Custom Palette Editor</span> <span id="head">WLED Palette Editor</span>
</h1> </h1>
</div> </div>
<div id="parent-container"> <div id="pCont"><div id="gBox"></div></div>
<div id="gradient-box"></div>
</div>
<div style="display: flex; justify-content: center;"> <div style="display: flex; justify-content: center;">
<div id="palettes" class="palettesMain"> <div id="pals" class="pMain">
<div id="distDiv" class="palTop"></div> <div id="distDiv" class="pTop"></div>
<div id="palTop" class="palTop"> <div id="memWarn" class="pTop" style="display:none; color:#ff6600; margin-bottom:8px; font-size:16px;">
Currently in use custom palettes Warning: Adding many custom palettes might cause stability issues, create <a href="/settings/sec#backup" style="color:#ff9900">backups</a> before proceeding.</div>
</div> <div id="pTop" class="pTop">Custom palettes</div>
</div> </div>
</div> </div>
<div style="display: flex; justify-content: center;"> <div style="display: flex; justify-content: center;">
<div id="info"> <div id="info">Click gradient to add. Box = color. Red = delete. Arrow = upload. Pencil = edit.</div>
Click on the gradient editor to add new color slider, then the colored box below the slider to change its color.
Click the red box below indicator (and confirm) to delete.
Once finished, click the arrow icon to upload into the desired slot.
To edit existing palette, click the pencil icon.
</div>
</div> </div>
<div style="display: flex; justify-content: center;"> <div style="display: flex; justify-content: center;">
<div id="staticPalettes" class="palettesMain"> <div id="sPals" class="pMain">
<div id="statpalTop" class="palTop"> <div id="spTop" class="pTop">Static palettes</div>
Available static palettes
</div>
</div> </div>
</div> </div>
</body> </body>
<script type="text/javascript"> <script type="text/javascript">
//global variables // global vars
var gradientBox = gId('gradient-box'); var gBox = gId('gBox'); // gradientBox
var cpalc = -1; var cpalc = -1, cpalm = 10; // current palette count, max custom
var pxCol = {}; var pxCol = {}; // pixel color map
var tCol = {}; var tCol = {}; // true color map
var rect = gradientBox.getBoundingClientRect(); var rect = gBox.getBoundingClientRect(); // bounding rect of gBox
var gradientLength = rect.width; var gLen = rect.width; // gradientLength
var mOffs = Math.round((gradientLength / 256) / 2) - 5; var mOffs = Math.round((gLen / 256) / 2) - 5; // marker offset
var paletteArray = []; //Holds the palettes after load. var palArr = []; // paletteArray
var paletteName = []; // Holds the names of the palettes after load. var palNm = []; // paletteName
var svgSave = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M7,12L12,17V14H16V10H12V7L7,12Z"/></svg>' var svgSave = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2A10,10 0 0,1 22,12M7,12L12,17V14H16V10H12V7L7,12Z"/></svg>'
var svgEdit = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M15.1,7.07C15.24,7.07 15.38,7.12 15.5,7.23L16.77,8.5C17,8.72 17,9.07 16.77,9.28L15.77,10.28L13.72,8.23L14.72,7.23C14.82,7.12 14.96,7.07 15.1,7.07M13.13,8.81L15.19,10.87L9.13,16.93H7.07V14.87L13.13,8.81Z"/></svg>' var svgEdit = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M12,2C6.47,2 2,6.47 2,12C2,17.53 6.47,22 12,22C17.53,22 22,17.53 22,12C22,6.47 17.53,2 12,2M15.1,7.07C15.24,7.07 15.38,7.12 15.5,7.23L16.77,8.5C17,8.72 17,9.07 16.77,9.28L15.77,10.28L13.72,8.23L14.72,7.23C14.82,7.12 14.96,7.07 15.1,7.07M13.13,8.81L15.19,10.87L9.13,16.93H7.07V14.87L13.13,8.81Z"/></svg>'
var svgDist = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M4 22H2V2H4V22M22 2H20V22H22V2M13.5 7H10.5V17H13.5V7Z"/></svg>' var svgDist = '<svg style="width:25px;height:25px" viewBox="0 0 24 24"><path fill=#fff d="M4 22H2V2H4V22M22 2H20V22H22V2M13.5 7H10.5V17H13.5V7Z"/></svg>'
var svgTrash = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30px" height="30px"><path style="fill:#880000; stroke: #888888; stroke-width: -2px;stroke-dasharray: 0.1, 8;" d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"/></svg>' var svgTrash = '<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" width="30px" height="30px"><path style="fill:#880000; stroke: #888888; stroke-width: -2px;stroke-dasharray: 0.1, 8;" d="M9,3V4H4V6H5V19A2,2 0 0,0 7,21H17A2,2 0 0,0 19,19V6H20V4H15V3H9M7,6H17V19H7V6M9,8V17H11V8H9M13,8V17H15V8H13Z"/></svg>'
const distDiv = gId("distDiv"); const distDiv = gId("distDiv");
distDiv.addEventListener('click', distribute); distDiv.addEventListener('click', distrib);
distDiv.setAttribute('title', 'Distribute colors equally'); distDiv.setAttribute('title', 'Distribute equally');
distDiv.innerHTML = svgDist; distDiv.innerHTML = svgDist;
function recOf() { function recOf() {
rect = gradientBox.getBoundingClientRect(); rect = gBox.getBoundingClientRect();
gradientLength = rect.width; gLen = rect.width;
mOffs = Math.round((gradientLength / 256) / 2) - 5; mOffs = Math.round((gLen / 256) / 2) - 5;
} }
//Initiation //Initiation
@@ -224,277 +181,220 @@
window.addEventListener('load', chkW); window.addEventListener('load', chkW);
window.addEventListener('resize', chkW); window.addEventListener('resize', chkW);
gradientBox.addEventListener("click", clikOnGradient); gBox.addEventListener("click", clikGrad);
//Sets start and stop, mandatory //Sets start and stop, mandatory
addC(0); addC(0);
addC(255); addC(255);
updateGradient(); //Sets the gradient at startup updGrad(); // updateGradient at startup
function clikOnGradient(e) { function clikGrad(e) { // clickOnGradient
removeTrashcan(e); rmTrash(e); // removeTrashcan
addC(Math.round((e.offsetX/gradientLength)*256)); addC(Math.round((e.offsetX/gLen)*256));
} }
///////// Add a new colorMarker ///////// Add a new color marker
function addC(truePos, thisColor = '') { function addC(tPos, thisCol = '') {
let position = -1; let pos = -1;
let iExist = false; let exist = false;
const colorMarkers = gradientBox.querySelectorAll('.color-marker'); const cMarks = gBox.querySelectorAll('.cMark'); // color markers
colorMarkers.forEach((colorMarker, i) => { cMarks.forEach((cm) => {
if (colorMarker.getAttribute("data-truepos") == truePos) { if (cm.getAttribute("data-tpos") == tPos) exist = true;
iExist = true;
}
}); });
if (colorMarkers.length > 17) iExist = true; if (cMarks.length > 17) exist = true;
if (iExist) return; // Exit the function early if iExist is true if (exist) return;
if (truePos > 0 && truePos < 255) { if (tPos > 0 && tPos < 255) {
//calculate first available > 0 for (var i=1; i<=16 && pos<1; i++) {
for (var i = 1; i <= 16 && position < 1; i++) { if (!gId("cMark"+i)) pos = i;
if (!gId("colorMarker"+i)) {
position = i;
}
} }
} else{ } else {
position = truePos; pos = tPos;
} }
if (thisColor == ''){ if (thisCol == '') {
thisColor = `#${(Math.random() * 0xFFFFFF << 0).toString(16).padStart(6, '0')}`;// set random color as default thisCol = `#${(Math.random()*0xFFFFFF<<0).toString(16).padStart(6,'0')}`;
} }
const colorMarker = cE('span'); // create a marker for the color position const cMark = cE('span'); // color marker
colorMarker.className = 'color-marker'; cMark.className = 'cMark';
colorMarker.id = 'colorMarker' + position.toString(); cMark.id = 'cMark' + pos;
colorMarker.setAttribute("data-truepos", truePos); //Used to always have a true position no matter what screen or percentage we use cMark.setAttribute("data-tpos", tPos);
colorMarker.setAttribute("data-truecol", thisColor); //Used to hold the color of the position in the gradient connected to a true position cMark.setAttribute("data-tcol", thisCol);
colorMarker.setAttribute("data-offset", mOffs); cMark.setAttribute("data-offset", mOffs);
colorMarker.addEventListener('click', stopFurtherProp); //Added to prevent the gradient click to fire when covered by a marker cMark.addEventListener('click', stopProp);
colorMarker.style.left = `${Math.round((gradientLength / 256) * truePos)+mOffs}px`; cMark.style.left = `${Math.round((gLen/256)*tPos)+mOffs}px`;
const colorPicker = cE('input'); const cPick = cE('input'); // colorPicker
colorPicker.type = 'color'; cPick.type = 'color';
colorPicker.value = thisColor; cPick.value = thisCol;
colorPicker.className = 'color-picker'; cPick.className = 'cPick';
colorPicker.id = 'colorPicker' + position.toString(); cPick.id = 'cPick' + pos;
colorPicker.addEventListener('input', updateGradient); cPick.addEventListener('input', updGrad);
colorPicker.addEventListener('click',cpClk) cPick.addEventListener('click', cpClk);
const colorPickerMarker = cE('span'); // create a marker for the color position const cPM = cE('span'); // colorPickerMarker
colorPickerMarker.className = 'color-picker-marker'; cPM.className = 'cPickMark';
colorPickerMarker.id = 'colorPickerMarker' + position.toString(); cPM.id = 'cPM' + pos;
colorPickerMarker.addEventListener('click', colClk); cPM.addEventListener('click', colClk);
colorPickerMarker.style.left = colorMarker.style.left; cPM.style.left = cMark.style.left;
colorPicker.style.left = colorMarker.style.left; cPick.style.left = cMark.style.left;
const deleteMarker = cE('span'); // create a delete marker for the color position if (pos > 0 && pos < 255) {
if (position > 0 && position < 255) { const dMark = cE('span'); // deleteMarker
deleteMarker.className = 'delete-marker'; dMark.className = 'dMark';
deleteMarker.id = 'deleteMarker' + position.toString(); dMark.id = 'dMark' + pos;
deleteMarker.addEventListener('click', (e) => { dMark.addEventListener('click', (e) => { delCol(e); });
deleteColor(e); dMark.style.left = cMark.style.left;
}); gBox.appendChild(dMark);
deleteMarker.style.left = colorMarker.style.left
} }
colorMarker.style.backgroundColor = colorPicker.value; // set marker color to match color picker cMark.style.backgroundColor = cPick.value;
colorPickerMarker.style.backgroundColor = colorPicker.value; cPM.style.backgroundColor = cPick.value;
gradientBox.appendChild(colorPicker); gBox.appendChild(cPick);
gradientBox.appendChild(colorMarker); gBox.appendChild(cMark);
gradientBox.appendChild(colorPickerMarker); gBox.appendChild(cPM);
if (position != 0 && position != 255) gradientBox.appendChild(deleteMarker); // append the marker if not start or end if (pos > 0 && pos < 255) mkDrag(gId(cMark.id)); // makeMeDrag
//make markers slidable IF they are not the first or last slider
if (position > 0 && position < 255) makeMeDrag(gId(colorMarker.id));
setTooltipMarker(gId(colorMarker.id)); setTip(gId(cMark.id)); // setTooltipMarker
updGrad();
updateGradient();
} }
///////// Update Gradient ///////// Update Gradient
function updateGradient() { function updGrad() { // updateGradient
const colorMarkers = gradientBox.querySelectorAll('.color-marker'); const cMarks = gBox.querySelectorAll('.cMark');
pxCol = {}; pxCol = {};
tCol = {} tCol = {};
colorMarkers.forEach((colorMarker, index) => { cMarks.forEach((cm) => {
const thisColorPicker = gId(colorMarkers[index].id.replace('colorMarker', 'colorPicker')); const cp = gId(cm.id.replace('cMark','cPick'));
const colorToSet = thisColorPicker.value; const col = cp.value;
gId(colorMarkers[index].id.replace('colorMarker', 'colorPickerMarker')).style.backgroundColor = colorToSet; gId(cm.id.replace('cMark','cPM')).style.backgroundColor = col;
colorMarkers[index].style.backgroundColor = colorToSet; cm.style.backgroundColor = col;
colorMarkers[index].setAttribute("data-truecol", colorToSet); cm.setAttribute("data-tcol", col);
const tPos = colorMarkers[index].getAttribute("data-truepos"); const tPos = cm.getAttribute("data-tpos");
const gradientPos = Math.round((gradientLength / 256)*tPos); const gPos = Math.round((gLen/256)*tPos);
pxCol[gradientPos] = colorToSet; pxCol[gPos] = col;
tCol[tPos] = colorToSet; tCol[tPos] = col;
}); });
gradientString = 'linear-gradient(to right'; let gStr = 'linear-gradient(to right';
Object.entries(pxCol).forEach(([p, c]) => { Object.entries(pxCol).forEach(([p,c]) => {
gradientString += `, ${c} ${p}px`; gStr += `, ${c} ${p}px`;
}); });
gradientString += ')'; gStr += ')';
gradientBox.style.background = gradientString; gBox.style.background = gStr;
//gId("jsonstring").innerHTML = calcJSON();
} }
function stopFurtherProp(e) { function stopProp(e) { e.stopPropagation(); }
e.stopPropagation();
}
function colClk(e){ function colClk(e) {
removeTrashcan(e) rmTrash(e);
e.stopPropagation(); e.stopPropagation();
let cp = gId(e.srcElement.id.replace("Marker","")); const src = e.target || e.srcElement;
let cp = gId(src.id.replace("M","")); // marker → picker
cp.click(); cp.click();
} }
function cpClk(e) { function cpClk(e) {
removeTrashcan(event) rmTrash(e);
e.stopPropagation(); e.stopPropagation();
} }
//This neat little function makes any element draggable on the X-axis. // make element draggable
//Just call: makeMeDrag(myElement); And you are good to go. function mkDrag(el) { // makeMeDrag
function makeMeDrag(elmnt) { var posNew=0, mPos=0;
var posNew = 0, mousePos = 0, mouseOffset = 0 var rect=gBox.getBoundingClientRect();
var maxX=rect.right, minX=rect.left, gLen=maxX-minX+1;
//Set these to whatever you want to limit your movement to el.onmousedown=dragStart;
var rect = gradientBox.getBoundingClientRect(); el.ontouchstart=dragStart;
var maxX = rect.right; // maximum X coordinate
var minX = rect.left; // minimum X coordinate i.e. also offset from left of screen
var gradientLength = maxX - minX + 1;
elmnt.onmousedown = dragMouseDown; function dragStart(e) {
elmnt.ontouchstart = dragMouseDown; rmTrash(e);
var isT=e.type.startsWith('touch');
function dragMouseDown(e) { if (!isT) e.preventDefault();
removeTrashcan(event) mPos=isT?e.touches[0].clientX:e.clientX;
e = e || window.event; d.onmouseup=dragEnd; d.ontouchend=dragEnd; d.ontouchcancel=dragEnd;
var isTouch = e.type.startsWith('touch'); d.onmousemove=dragMove; d.ontouchmove=dragMove;
if (!isTouch) e.preventDefault();
// get the mouse cursor position at startup:
mousePos = isTouch ? e.touches[0].clientX : e.clientX;
d.onmouseup = closeDragElement;
d.ontouchcancel = closeDragElement;
d.ontouchend = closeDragElement;
// call a function whenever the cursor moves:
d.onmousemove = elementDrag;
d.ontouchmove = elementDrag;
} }
function elementDrag(e) { function dragMove(e) {
e = e || window.event; var isT=e.type.startsWith('touch');
var isTouch = e.type.startsWith('touch'); if (!isT) e.preventDefault();
if (!isTouch) e.preventDefault(); var cX=isT?e.touches[0].clientX:e.clientX;
// calculate the new cursor position: posNew=mPos-cX; mPos=cX;
var clientX = isTouch ? e.touches[0].clientX : e.clientX; var mInG=mPos-(minX+1);
posNew = mousePos - clientX; var tPos=Math.round((mInG/gLen)*256);
mousePos = clientX; var old=el.getAttribute("data-tpos");
mousePosInGradient = mousePos - (minX + 1) if (tPos>0 && tPos<255 && old!=tPos) {
el.style.left=(Math.round((gLen/256)*tPos)+mOffs)+"px";
truePos = Math.round((mousePosInGradient/gradientLength)*256); gId(el.id.replace('cMark','cPM')).style.left=el.style.left;
oldTruePos = elmnt.getAttribute("data-truepos"); gId(el.id.replace('cMark','dMark')).style.left=el.style.left;
// set the element's new position if new position within min/max limits: gId(el.id.replace('cMark','cPick')).style.left=el.style.left;
if (truePos > 0 && truePos < 255 && oldTruePos != truePos) { el.setAttribute("data-tpos",tPos);
if (truePos < 64) { setTip(el);
thisOffset = 0; updGrad();
} else if (truePos > 192) {
thisOffset = 7;
} else {
thisOffset=3;
}
elmnt.style.left = (Math.round((gradientLength/256)*truePos)+mOffs) + "px";
gId(elmnt.id.replace('colorMarker', 'colorPickerMarker')).style.left = elmnt.style.left;
gId(elmnt.id.replace('colorMarker', 'deleteMarker')).style.left = elmnt.style.left;
gId(elmnt.id.replace('colorMarker', 'colorPicker')).style.left = elmnt.style.left;
elmnt.setAttribute("data-truepos", truePos);
setTooltipMarker(elmnt);
updateGradient();
} }
} }
function closeDragElement() { function dragEnd() {
/* stop moving when mouse button is released:*/ d.onmouseup=null; d.ontouchend=null; d.ontouchcancel=null;
d.onmouseup = null; d.onmousemove=null; d.ontouchmove=null;
d.ontouchcancel = null;
d.ontouchend = null;
d.onmousemove = null;
d.ontouchmove = null;
} }
} }
function setTooltipMarker(elmnt) { function setTip(el) { // setTooltipMarker
elmnt.setAttribute('title', `${elmnt.getAttribute("data-truepos")} : ${elmnt.getAttribute("data-truecol")}`) el.setAttribute('title', `${el.getAttribute("data-tpos")} : ${el.getAttribute("data-tcol")}`);
} }
function deleteColor(e) { function delCol(e) { // deleteColor
var trash = cE("div"); var trash=cE("div");
thisDeleteMarker = e.srcElement; var dM=e.target || e.srcElement;
thisColorMarker = gId(thisDeleteMarker.id.replace("delete", "color")); var cM=gId(dM.id.replace("d","c"));
thisColorPickerMarker = gId(thisDeleteMarker.id.replace("delete", "colorPicker")); var cPM=gId(dM.id.replace("dMark","cPM"));
thisColorPicker = gId(thisDeleteMarker.id.replace("deleteMarker", "colorPicker")); var cP=gId(dM.id.replace("dMark","cPick"));
renderOffsetX = 15 - 5; var rX=dM.getBoundingClientRect().x-10;
renderX = e.srcElement.getBoundingClientRect().x - renderOffsetX; var rY=dM.getBoundingClientRect().y+13;
renderY = e.srcElement.getBoundingClientRect().y + 13;
trash.id = "trash"; trash.id="trash";
trash.innerHTML = svgTrash; trash.innerHTML=svgTrash;
trash.style.position = "absolute"; trash.style.position="absolute";
trash.style.left = (renderX) + "px"; trash.style.left=rX+"px";
trash.style.top = (renderY) + "px"; trash.style.top=rY+"px";
d.body.appendChild(trash); d.body.appendChild(trash);
trash.addEventListener("click", (e)=>{ trash.addEventListener("click",()=>{
trash.parentNode.removeChild(trash); trash.remove(); cM.remove(); cPM.remove(); cP.remove(); dM.remove();
thisDeleteMarker.parentNode.removeChild(thisDeleteMarker); updGrad();
thisColorPickerMarker.parentNode.removeChild(thisColorPickerMarker);
thisColorMarker.parentNode.removeChild(thisColorMarker);
thisColorPicker.parentNode.removeChild(thisColorPicker);
updateGradient();
}); });
e.stopPropagation(); e.stopPropagation();
// Add event listener to close the trashcan on outside click d.addEventListener("click", rmTrash);
d.addEventListener("click", removeTrashcan);
e.stopPropagation();
} }
function removeTrashcan(event) { function rmTrash(e) { // removeTrashcan
trash = gId("trash"); var t=gId("trash");
if (event.target != trash && trash) { if (t && e.target!=t) { t.remove(); d.removeEventListener("click", rmTrash);}
trash.parentNode.removeChild(trash);
d.removeEventListener("click", removeTrashcan);
}
} }
function chkW() { function chkW() {
//Possibly add more code that recalculates the gradient... Massive job ;) const wrap=gId('wrap'); const head=gId('head');
const wrap = gId('wrap'); head.style.display=(wrap.offsetWidth<600)?'none':'inline';
const head = gId('head');
if (wrap.offsetWidth < 600) {
head.style.display = 'none';
} else {
head.style.display = 'inline';
}
} }
function calcJSON() { function calcJSON() {
let rStr = '{"palette":[' let rStr='{"palette":[';
Object.entries(tCol).forEach(([p, c]) => { Object.entries(tCol).forEach(([p,c],i)=>{
if (p > 0) rStr += ','; if (i>0) rStr+=',';
rStr += `${p},"${c.slice(1)}"`; // store in hex notation rStr+=`${p},"${c.slice(1)}"`;
//rStr += `${p},${parseInt(c.slice(1, 3), 16)},${parseInt(c.slice(3, 5), 16)},${parseInt(c.slice(5, 7), 16)}`;
}); });
rStr += ']}'; rStr+=']}';
return rStr; return rStr;
} }
function initiateUpload(idx) { function initUpload(i) {
const data = calcJSON(); uploadJSON(calcJSON(), `/palette${i}.json`);
const fileName = `/palette${idx}.json`;
uploadJSON(data, fileName);
} }
function uploadJSON(jsonString, fileName) { function uploadJSON(jsonString, fileName) {
@@ -525,41 +425,38 @@
} }
async function getInfo() { async function getInfo() {
hst = location.host; getLoc();
if (hst.length > 0 ) { try {
try { var arr = [];
var arr = []; const resInfo = await fetch(getURL('/json/info')); // fetch info (includes cpalcount and cpalmax)
const responseInfo = await fetch('http://'+hst+'/json/info'); const resPals = await fetch(getURL('/json/pal')); // fetch palette names
const responsePalettes = await fetch('http://'+hst+'/json/palettes'); const json = await resInfo.json();
const json = await responseInfo.json(); palNm = await resPals.json();
paletteName = await responsePalettes.json(); cpalc = json.cpalcount;
cpalc = json.cpalcount; cpalm = json.cpalmax;
fetchPalettes(cpalc-1); fetchPals(cpalc-1);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
}
} else {
console.error('cannot identify host');
} }
} }
async function fetchPalettes(lastPal) { async function fetchPals(lastPal) {
paletteArray.length = 0; palArr.length = 0;
for (let i = 0; i <= lastPal; i++) { for (let i = 0; i <= lastPal; i++) {
const url = `http://${hst}/palette${i}.json`; const url = getURL(`/palette${i}.json`);
try { try {
const response = await fetch(url); const response = await fetch(url);
const json = await response.json(); const json = await response.json();
paletteArray.push(json); palArr.push(json);
} catch (error) { } catch (error) {
cpalc--; //remove audio/dynamically generated palettes cpalc--; //remove audio/dynamically generated palettes
console.error(`Error fetching JSON from ${url}: `, error); console.error(`Error fetching JSON from ${url}: `, error);
} }
} }
//If there is room for more custom palettes, add an empty, gray slot //If there is room for more custom palettes, add an empty, gray slot
if (paletteArray.length < 10) { if (palArr.length < cpalm) {
//Room for one more :) //Room for one more :)
paletteArray.push({"palette":[0,70,70,70,255,70,70,70]}); palArr.push({"palette":[0,70,70,70,255,70,70,70]});
} }
//Get static palettes from localStorage and do some magic to reformat them into the same format as the palette JSONs //Get static palettes from localStorage and do some magic to reformat them into the same format as the palette JSONs
@@ -569,12 +466,12 @@
const wledPalx = JSON.parse(localStorage.getItem('wledPalx')); const wledPalx = JSON.parse(localStorage.getItem('wledPalx'));
if (!wledPalx) { if (!wledPalx) {
alert("The cache of palettes are missig from your browser. You should probably return to the main page and let it load properly for the palettes cache to regenerate before returning here.","Missing cached palettes!") alert("Palette cache missing from browser. Return to main page first.","Missing cache!")
} else { } else {
for (const key in wledPalx.p) { for (const key in wledPalx.p) {
wledPalx.p[key].name = paletteName[key]; wledPalx.p[key].name = palNm[key];
if (key > 245) { if (key > 255-cpalm) {
delete wledPalx.p[key]; delete wledPalx.p[key]; // remove custom palettes
continue; continue;
} }
const arr = wledPalx.p[key]; const arr = wledPalx.p[key];
@@ -610,130 +507,133 @@
// Sort pArray by name // Sort pArray by name
pArray.sort((a, b) => a.name.localeCompare(b.name)); pArray.sort((a, b) => a.name.localeCompare(b.name));
paletteArray.push( ...pArray); palArr.push( ...pArray);
} }
generatePaletteDivs(); genPalDivs();
} }
function generatePaletteDivs() { function genPalDivs() {
const palettesDiv = gId("palettes"); const palsDiv = gId("pals");
const staticPalettesDiv = gId("staticPalettes"); const sPalsDiv = gId("sPals");
const paletteDivs = Array.from(palettesDiv.children).filter((child) => { const memWarn = gId("memWarn");
return child.id.match(/^palette\d$/); // match only elements with id starting with "palette" followed by a single digit const palDivs = Array.from(palsDiv.children).filter((child) => {
return /^pal\d+$/.test(child.id); // match ids "pal" followed by one or more digits
}); });
for (const div of paletteDivs) { for (const div of palDivs) {
palettesDiv.removeChild(div); // remove each div that matches the above selector palsDiv.removeChild(div); // remove each div that matches the above selector
} }
for (let i = 0; i < paletteArray.length; i++) { memWarn.style.display = (cpalc >= 10) ? 'block' : 'none'; // Show/hide memory warning based on custom palette count
const palette = paletteArray[i];
const paletteDiv = cE("div");
paletteDiv.id = `palette${i}`;
paletteDiv.classList.add("palette");
const thisKey = Object.keys(palette)[0];
paletteDiv.dataset.colarray = JSON.stringify(palette[thisKey]);
const gradientDiv = cE("div"); for (let i = 0; i < palArr.length; i++) {
gradientDiv.id = `paletteGradient${i}` const pal = palArr[i];
const buttonsDiv = cE("div"); const palDiv = cE("div");
buttonsDiv.id = `buttonsDiv${i}`; palDiv.id = `pal${i}`;
buttonsDiv.classList.add("buttonsDiv") palDiv.classList.add("pal");
const thisKey = Object.keys(pal)[0];
palDiv.dataset.colarray = JSON.stringify(pal[thisKey]);
const sendSpan = cE("span"); const gradDiv = cE("div");
sendSpan.id = `sendSpan${i}`; gradDiv.id = `pGrad${i}`
sendSpan.onclick = function() {initiateUpload(i)}; const btnsDiv = cE("div");
sendSpan.setAttribute('title', `Send current editor to slot ${i}`); // perhaps Save instead of Send? btnsDiv.id = `btns${i}`;
sendSpan.innerHTML = svgSave; btnsDiv.classList.add("btnsDiv")
sendSpan.classList.add("sendSpan")
const editSpan = cE("span"); const sSpan = cE("span");
editSpan.id = `editSpan${i}`; sSpan.id = `s${i}`;
editSpan.onclick = function() {loadForEdit(i)}; sSpan.onclick = function() {initUpload(i)};
editSpan.setAttribute('title', `Copy slot ${i} palette to editor`); sSpan.setAttribute('title', `Send current editor to slot ${i}`); // perhaps Save instead of Send?
if (paletteArray[i].name) { sSpan.innerHTML = svgSave;
editSpan.setAttribute('title', `Copy ${paletteArray[i].name} palette to editor`); sSpan.classList.add("sSpan")
const eSpan = cE("span");
eSpan.id = `e${i}`;
eSpan.onclick = function() {loadEdit(i)};
eSpan.setAttribute('title', `Copy slot ${i} to editor`);
if (palArr[i].name) {
eSpan.setAttribute('title', `Copy ${palArr[i].name} to editor`);
} }
editSpan.innerHTML = svgEdit; eSpan.innerHTML = svgEdit;
editSpan.classList.add("editSpan") eSpan.classList.add("eSpan")
gradientDiv.classList.add("paletteGradients"); gradDiv.classList.add("pGrads");
let gradientColors = ""; let gCols = "";
for (let j = 0; j < palette[thisKey].length; j += 2) { for (let j = 0; j < pal[thisKey].length; j += 2) {
const position = palette[thisKey][j]; const pos = pal[thisKey][j];
if (typeof(palette[thisKey][j+1]) === "string") { if (typeof(pal[thisKey][j+1]) === "string") {
gradientColors += `#${palette[thisKey][j+1]} ${position/255*100}%, `; gCols += `#${pal[thisKey][j+1]} ${pos/255*100}%, `;
} else { } else {
const red = palette[thisKey][j + 1]; const r = pal[thisKey][j + 1];
const green = palette[thisKey][j + 2]; const g = pal[thisKey][j + 2];
const blue = palette[thisKey][j + 3]; const b = pal[thisKey][j + 3];
gradientColors += `rgba(${red}, ${green}, ${blue}, 1) ${position/255*100}%, `; gCols += `rgba(${r}, ${g}, ${b}, 1) ${pos/255*100}%, `;
j += 2; j += 2;
} }
} }
gradientColors = gradientColors.slice(0, -2); // remove the last comma and space gCols = gCols.slice(0, -2); // remove the last comma and space
gradientDiv.style.backgroundImage = `linear-gradient(to right, ${gradientColors})`; gradDiv.style.backgroundImage = `linear-gradient(to right, ${gCols})`;
paletteDiv.className = "palGradientParent"; palDiv.className = "pGradPar";
if (thisKey == "palette") { if (thisKey == "palette") {
buttonsDiv.appendChild(sendSpan); //Only offer to send to custom palettes btnsDiv.appendChild(sSpan); //Only offer to send to custom palettes
} else{ } else{
editSpan.style.marginLeft = "25px"; eSpan.style.marginLeft = "25px";
} }
if (i!=cpalc) { if (i!=cpalc) {
buttonsDiv.appendChild(editSpan); //Dont offer to edit the empty spot btnsDiv.appendChild(eSpan); //Dont offer to edit the empty spot
} }
paletteDiv.appendChild(gradientDiv); palDiv.appendChild(gradDiv);
paletteDiv.appendChild(buttonsDiv); palDiv.appendChild(btnsDiv);
if (thisKey == "palette") { if (thisKey == "palette") {
palettesDiv.appendChild(paletteDiv); palsDiv.appendChild(palDiv);
} else { } else {
staticPalettesDiv.appendChild(paletteDiv); sPalsDiv.appendChild(palDiv);
} }
} }
} }
function loadForEdit(i) { function loadEdit(i) {
d.querySelectorAll('input[id^="colorPicker"]').forEach((input) => { d.querySelectorAll('input[id^="cPick"]').forEach((input) => {
input.parentNode.removeChild(input); input.parentNode.removeChild(input);
}); });
d.querySelectorAll('span[id^="colorMarker"], span[id^="colorPickerMarker"], span[id^="deleteMarker"]').forEach((span) => { d.querySelectorAll('span[id^="cMark"], span[id^="cPM"], span[id^="dMark"]').forEach((span) => {
span.parentNode.removeChild(span); span.parentNode.removeChild(span);
}); });
let colArray = JSON.parse(gId(`palette${i}`).getAttribute("data-colarray")); let colArr = JSON.parse(gId(`pal${i}`).getAttribute("data-colarray"));
for (let j = 0; j < colArray.length; j += 2) { for (let j = 0; j < colArr.length; j += 2) {
const position = colArray[j]; const pos = colArr[j];
let hex; let hex;
if (typeof(colArray[j+1]) === "string") { if (typeof(colArr[j+1]) === "string") {
hex = `#${colArray[j+1]}`; hex = `#${colArr[j+1]}`;
} else { } else {
const red = colArray[j + 1]; const r = colArr[j + 1];
const green = colArray[j + 2]; const g = colArr[j + 2];
const blue = colArray[j + 3]; const b = colArr[j + 3];
hex = rgbToHex(red, green, blue); hex = rgbToHex(r, g, b);
j += 2; j += 2;
} }
addC(position, hex); addC(pos, hex);
window.scroll(0, 0); window.scroll(0, 0);
} }
} }
function distribute() { function distrib() {
let colorMarkers = [...gradientBox.querySelectorAll('.color-marker')]; let cMarks = [...gBox.querySelectorAll('.cMark')];
colorMarkers.sort((a, b) => a.getAttribute('data-truepos') - b.getAttribute('data-truepos')); cMarks.sort((a, b) => a.getAttribute('data-tpos') - b.getAttribute('data-tpos'));
colorMarkers = colorMarkers.slice(1, -1); cMarks = cMarks.slice(1, -1);
const spacing = Math.round(256 / (colorMarkers.length + 1)); const spacing = Math.round(256 / (cMarks.length + 1));
colorMarkers.forEach((e, i) => { cMarks.forEach((e, i) => {
const markerId = e.id.match(/\d+/)[0]; const mId = e.id.match(/\d+/)[0];
const trueCol = e.getAttribute("data-truecol"); const tCol = e.getAttribute("data-tcol");
gradientBox.removeChild(e); gBox.removeChild(e);
gradientBox.removeChild(gId(`colorPicker${markerId}`)); gBox.removeChild(gId(`cPick${mId}`));
gradientBox.removeChild(gId(`colorPickerMarker${markerId}`)); gBox.removeChild(gId(`cPM${mId}`));
gradientBox.removeChild(gId(`deleteMarker${markerId}`)); gBox.removeChild(gId(`dMark${mId}`));
addC(spacing * (i + 1), trueCol); addC(spacing * (i + 1), tCol);
}); });
} }

View File

@@ -1042,8 +1042,7 @@ function genPalPrevCss(id)
} }
var gradient = []; var gradient = [];
for (let j = 0; j < paletteData.length; j++) { paletteData.forEach((e,j) => {
const e = paletteData[j];
let r, g, b; let r, g, b;
let index = false; let index = false;
if (Array.isArray(e)) { if (Array.isArray(e)) {
@@ -1065,9 +1064,8 @@ function genPalPrevCss(id)
if (index === false) { if (index === false) {
index = Math.round(j / paletteData.length * 100); index = Math.round(j / paletteData.length * 100);
} }
gradient.push(`rgb(${r},${g},${b}) ${index}%`); gradient.push(`rgb(${r},${g},${b}) ${index}%`);
} });
return `background: linear-gradient(to right,${gradient.join()});`; return `background: linear-gradient(to right,${gradient.join()});`;
} }
@@ -3088,11 +3086,9 @@ function unify(e) { return e.changedTouches ? e.changedTouches[0] : e; }
function hasIroClass(classList) function hasIroClass(classList)
{ {
for (var i = 0; i < classList.length; i++) { let found = false;
var element = classList[i]; classList.forEach((e)=>{ if (e.startsWith('Iro')) found = true; });
if (element.startsWith('Iro')) return true; return found;
}
return false;
} }
//required by rangetouch.js //required by rangetouch.js
function lock(e) function lock(e)

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>WLED Settings</title> <title>WLED Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
function S() { function S() {
getLoc(); getLoc();

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>2D Set-up</title> <title>2D Set-up</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
var maxPanels=64; var maxPanels=64;
var ctx = null; var ctx = null;

View File

@@ -4,7 +4,7 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8"> <meta charset="utf-8">
<title>DMX Settings</title> <title>DMX Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
function HW(){window.open("https://kno.wled.ge/interfaces/dmx-output/");} function HW(){window.open("https://kno.wled.ge/interfaces/dmx-output/");}
function GCH(num) { function GCH(num) {

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>LED Settings</title> <title>LED Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32 var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[]; var customStarts=false,startsDirty=[];

View File

@@ -4,7 +4,7 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8"> <meta charset="utf-8">
<title>Misc Settings</title> <title>Misc Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
function U() { window.open(getURL("/update"),"_self"); } function U() { window.open(getURL("/update"),"_self"); }
function checkNum(o) { function checkNum(o) {

View File

@@ -4,7 +4,7 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8"> <meta charset="utf-8">
<title>Sync Settings</title> <title>Sync Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;} function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;}
else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} } else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} }

View File

@@ -4,7 +4,7 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8"> <meta charset="utf-8">
<title>Time Settings</title> <title>Time Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
var el=false; var el=false;
var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>UI Settings</title> <title>UI Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
var initial_ds, initial_st, initial_su, oldUrl; var initial_ds, initial_st, initial_su, oldUrl;
var sett = null; var sett = null;

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>Usermod Settings</title> <title>Usermod Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
var umCfg = {}; var umCfg = {};
var pins = [], pinO = [], owner; var pins = [], pinO = [], owner;

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>WiFi Settings</title> <title>WiFi Settings</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
var scanLoops = 0, preScanSSID = ""; var scanLoops = 0, preScanSSID = "";
var maxNetworks = 3; var maxNetworks = 3;

View File

@@ -3,7 +3,7 @@
<head> <head>
<meta content='width=device-width' name='viewport'> <meta content='width=device-width' name='viewport'>
<title>WLED Update</title> <title>WLED Update</title>
<script src="common.js" async type="text/javascript"></script> <script src="common.js" type="text/javascript"></script>
<script> <script>
function B() { window.history.back(); } function B() { window.history.back(); }
var cnfr = false; var cnfr = false;

View File

@@ -1,7 +1,5 @@
#include "wled.h" #include "wled.h"
#include "palettes.h"
#define JSON_PATH_STATE 1 #define JSON_PATH_STATE 1
#define JSON_PATH_INFO 2 #define JSON_PATH_INFO 2
#define JSON_PATH_STATE_INFO 3 #define JSON_PATH_STATE_INFO 3
@@ -771,7 +769,8 @@ void serializeInfo(JsonObject root)
root[F("fxcount")] = strip.getModeCount(); root[F("fxcount")] = strip.getModeCount();
root[F("palcount")] = getPaletteCount(); root[F("palcount")] = getPaletteCount();
root[F("cpalcount")] = customPalettes.size(); //number of custom palettes root[F("cpalcount")] = customPalettes.size(); // number of custom palettes
root[F("cpalmax")] = WLED_MAX_CUSTOM_PALETTES; // maximum number of custom palettes
JsonArray ledmaps = root.createNestedArray(F("maps")); JsonArray ledmaps = root.createNestedArray(F("maps"));
for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) { for (size_t i=0; i<WLED_MAX_LEDMAPS; i++) {
@@ -935,7 +934,7 @@ void serializePalettes(JsonObject root, int page)
#endif #endif
int customPalettesCount = customPalettes.size(); int customPalettesCount = customPalettes.size();
int palettesCount = getPaletteCount() - customPalettesCount; int palettesCount = getPaletteCount() - customPalettesCount; // palettesCount is number of palettes, not palette index
int maxPage = (palettesCount + customPalettesCount -1) / itemPerPage; int maxPage = (palettesCount + customPalettesCount -1) / itemPerPage;
if (page > maxPage) page = maxPage; if (page > maxPage) page = maxPage;
@@ -947,8 +946,8 @@ void serializePalettes(JsonObject root, int page)
root[F("m")] = maxPage; // inform caller how many pages there are root[F("m")] = maxPage; // inform caller how many pages there are
JsonObject palettes = root.createNestedObject("p"); JsonObject palettes = root.createNestedObject("p");
for (int i = start; i < end; i++) { for (int i = start; i <= end; i++) {
JsonArray curPalette = palettes.createNestedArray(String(i>=palettesCount ? 255 - i + palettesCount : i)); JsonArray curPalette = palettes.createNestedArray(String(i<=palettesCount ? i : 255 - (i - (palettesCount + 1))));
switch (i) { switch (i) {
case 0: //default palette case 0: //default palette
setPaletteColors(curPalette, PartyColors_p); setPaletteColors(curPalette, PartyColors_p);
@@ -977,8 +976,8 @@ void serializePalettes(JsonObject root, int page)
curPalette.add("c1"); curPalette.add("c1");
break; break;
default: default:
if (i >= palettesCount) if (i > palettesCount)
setPaletteColors(curPalette, customPalettes[i - palettesCount]); setPaletteColors(curPalette, customPalettes[i - (palettesCount + 1)]);
else if (i < 13) // palette 6 - 12, fastled palettes else if (i < 13) // palette 6 - 12, fastled palettes
setPaletteColors(curPalette, *fastledPalettes[i-6]); setPaletteColors(curPalette, *fastledPalettes[i-6]);
else { else {

5
wled00/palettes.h → wled00/palettes.cpp Executable file → Normal file
View File

@@ -1,5 +1,4 @@
#ifndef PalettesWLED_h #include "wled.h"
#define PalettesWLED_h
/* /*
* WLED Color palettes * WLED Color palettes
@@ -768,5 +767,3 @@ const uint8_t* const gGradientPalettes[] PROGMEM = {
candy2_gp, //70-57 Candy2 candy2_gp, //70-57 Candy2
trafficlight_gp //71-58 Traffic Light trafficlight_gp //71-58 Traffic Light
}; };
#endif

View File

@@ -230,7 +230,7 @@ uint8_t extractModeName(uint8_t mode, const char *src, char *dest, uint8_t maxLe
} else return 0; } else return 0;
} }
if (src == JSON_palette_names && mode > (GRADIENT_PALETTE_COUNT + 13)) { if (src == JSON_palette_names && mode > 255-customPalettes.size()) {
snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode); snprintf_P(dest, maxLen, PSTR("~ Custom %d ~"), 255-mode);
dest[maxLen-1] = '\0'; dest[maxLen-1] = '\0';
return strlen(dest); return strlen(dest);

View File

@@ -475,29 +475,31 @@ void initServer()
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) { server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
if (captivePortal(request)) return; if (captivePortal(request)) return;
if (!showWelcomePage || request->hasArg(F("sliders"))) { if (!showWelcomePage || request->hasArg(F("sliders"))) {
handleStaticContent(request, F("/index.htm"), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_index, PAGE_index_L); handleStaticContent(request, F("/index.htm"), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_index, PAGE_index_length);
} else { } else {
serveSettings(request); serveSettings(request);
} }
}); });
#ifdef WLED_ENABLE_PIXART #ifndef WLED_DISABLE_2D
#ifdef WLED_ENABLE_PIXART
static const char _pixart_htm[] PROGMEM = "/pixart.htm"; static const char _pixart_htm[] PROGMEM = "/pixart.htm";
server.on(_pixart_htm, HTTP_GET, [](AsyncWebServerRequest *request) { server.on(_pixart_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixart, PAGE_pixart_L); handleStaticContent(request, FPSTR(_pixart_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pixart, PAGE_pixart_length);
}); });
#endif #endif
#ifndef WLED_DISABLE_PXMAGIC #ifndef WLED_DISABLE_PXMAGIC
static const char _pxmagic_htm[] PROGMEM = "/pxmagic.htm"; static const char _pxmagic_htm[] PROGMEM = "/pxmagic.htm";
server.on(_pxmagic_htm, HTTP_GET, [](AsyncWebServerRequest *request) { server.on(_pxmagic_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pxmagic, PAGE_pxmagic_L); handleStaticContent(request, FPSTR(_pxmagic_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_pxmagic, PAGE_pxmagic_length);
}); });
#endif
#endif #endif
static const char _cpal_htm[] PROGMEM = "/cpal.htm"; static const char _cpal_htm[] PROGMEM = "/cpal.htm";
server.on(_cpal_htm, HTTP_GET, [](AsyncWebServerRequest *request) { server.on(_cpal_htm, HTTP_GET, [](AsyncWebServerRequest *request) {
handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_cpal, PAGE_cpal_L); handleStaticContent(request, FPSTR(_cpal_htm), 200, FPSTR(CONTENT_TYPE_HTML), PAGE_cpal, PAGE_cpal_length);
}); });
#ifdef WLED_ENABLE_WEBSOCKETS #ifdef WLED_ENABLE_WEBSOCKETS