diff --git a/package.json b/package.json
index c5dd01132..302312ce7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "wled",
- "version": "0.13.0-bl2",
+ "version": "0.13.0-bl3",
"description": "Tools for WLED project",
"main": "tools/cdata.js",
"directories": {
diff --git a/platformio.ini b/platformio.ini
index 611a6614f..87d85fd01 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -196,7 +196,7 @@ lib_deps =
${env.lib_deps}
# ESPAsyncTCP @ 1.2.0
ESPAsyncUDP
- makuna/NeoPixelBus @ 2.6.4 # 2.6.5 and newer do not compile on ESP core < 3.0.0
+ makuna/NeoPixelBus @ 2.6.7 # 2.6.5/2.6.6 do not compile on ESP core < 3.0.0
[esp32]
build_flags = -g
@@ -205,7 +205,7 @@ build_flags = -g
lib_deps =
${env.lib_deps}
- https://github.com/Makuna/NeoPixelBus.git # until next upstream release
+ makuna/NeoPixelBus @ 2.6.7
AsyncTCP @ 1.0.3
[esp32s2]
@@ -217,7 +217,7 @@ build_flags = -g
lib_deps =
${env.lib_deps}
- https://github.com/Makuna/NeoPixelBus.git # until next upstream release
+ makuna/NeoPixelBus @ 2.6.7
AsyncTCP @ 1.0.3
# ------------------------------------------------------------------------------
diff --git a/tools/cdata.js b/tools/cdata.js
index 8a9048435..fee10a54a 100644
--- a/tools/cdata.js
+++ b/tools/cdata.js
@@ -65,7 +65,7 @@ function adoptVersionAndRepo(html) {
return html;
}
-function writeHtmlGzipped(sourceFile, resultFile) {
+function writeHtmlGzipped(sourceFile, resultFile, page) {
console.info("Reading " + sourceFile);
new inliner(sourceFile, function (error, html) {
console.info("Inlined " + html.length + " characters");
@@ -95,8 +95,8 @@ function writeHtmlGzipped(sourceFile, resultFile) {
*/
// Autogenerated from ${sourceFile}, do not edit!!
-const uint16_t PAGE_index_L = ${result.length};
-const uint8_t PAGE_index[] PROGMEM = {
+const uint16_t PAGE_${page}_L = ${result.length};
+const uint8_t PAGE_${page}[] PROGMEM = {
${array}
};
`;
@@ -194,7 +194,8 @@ function writeChunks(srcDir, specs, resultFile) {
fs.writeFileSync(resultFile, src);
}
-writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h");
+writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index');
+writeHtmlGzipped("wled00/data/simple.htm", "wled00/html_simple.h", 'simple');
writeChunks(
"wled00/data",
diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp
index 930a42066..a83a1956c 100644
--- a/wled00/cfg.cpp
+++ b/wled00/cfg.cpp
@@ -23,6 +23,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) {
getStringFromJson(cmDNS, id[F("mdns")], 33);
getStringFromJson(serverDescription, id[F("name")], 33);
getStringFromJson(alexaInvocationName, id[F("inv")], 33);
+#ifndef WLED_DISABLE_SIMPLE_UI
+ CJSON(simplifiedUI, id[F("sui")]);
+#endif
JsonObject nw_ins_0 = doc["nw"]["ins"][0];
getStringFromJson(clientSSID, nw_ins_0[F("ssid")], 33);
@@ -480,6 +483,7 @@ void serializeConfig() {
id[F("mdns")] = cmDNS;
id[F("name")] = serverDescription;
id[F("inv")] = alexaInvocationName;
+ id[F("sui")] = simplifiedUI;
JsonObject nw = doc.createNestedObject("nw");
diff --git a/wled00/data/index.css b/wled00/data/index.css
index 3d6bd3590..da0a4cc48 100644
--- a/wled00/data/index.css
+++ b/wled00/data/index.css
@@ -116,6 +116,9 @@ button {
font-size: 24px;
line-height: 1;
display: inline-block;
+}
+
+.top .icons, .bot .icons {
margin: -2px 0 4px 0;
}
@@ -128,7 +131,7 @@ button {
width: 100%;
}
-.segt {
+.segt, .plentry TABLE {
table-layout: fixed;
width: 100%;
}
@@ -136,11 +139,13 @@ button {
.segt TD {
text-align: center;
/*text-transform: uppercase;*/
+}
+.segt TD, .plentry TD {
font-size: 14px;
padding: 0;
vertical-align: middle;
}
-.segt TD.h {
+.segt TD.h, .plentry TD.h {
font-size: 13px;
padding: 2px 0 0;
}
@@ -178,12 +183,12 @@ button {
}
.flr {
- float: right;
- cursor: pointer;
- margin: 0;
color: var(--c-f);
transform: rotate(0deg);
transition: transform 0.3s;
+ position: absolute;
+ top: 8px;
+ right: 8px;
}
.exp {
@@ -222,6 +227,7 @@ button {
transition: color 0.3s, background-color 0.3s;
font-size: 17px;
color: var(--c-c);
+ min-width: 44px;
}
.top button {
@@ -276,6 +282,19 @@ button {
-webkit-overflow-scrolling: touch;
}
+#segutil, #segutil2, #segcont, #putil, #pcont, #pql {
+ width: 280px;
+ margin: 0 auto;
+}
+
+#segutil .seg {
+ margin: 10px 0;
+}
+
+#segcont, #segutil, #putil {
+ padding: 10px 0 0;
+}
+
.smooth { transition: transform calc(var(--f, 1)*.5s) ease-out }
.tab-label {
@@ -300,7 +319,7 @@ button {
pointer-events: none;
}
-.staytop {
+.staytop, .staybot {
display: block;
position: -webkit-sticky;
position: sticky;
@@ -309,6 +328,10 @@ button {
margin: 0 auto auto;
}
+.staybot {
+ bottom: 0;
+}
+
#staytop, #staytop1 {
background: var(--c-2);
width: 310px;
@@ -324,11 +347,6 @@ button {
top: 58px;
}
-#fxb0 {
- margin-bottom: 2px;
- filter: drop-shadow(0 0 1px #000);
-}
-
.first {
margin-top: 10px;
}
@@ -336,6 +354,7 @@ button {
#toast {
opacity: 0;
background-color: var(--c-5);
+ border: 1px solid var(--c-2);
max-width: 90%;
color: var(--c-f);
text-align: center;
@@ -387,10 +406,6 @@ button {
margin: 12px 0;
}
-.valtd i {
- font-size: 14px;
-}
-
#roverstar {
position: fixed;
top: calc(var(--th) + 5px);
@@ -534,18 +549,6 @@ input[type=range]::-moz-range-thumb {
position: relative;
}
-.sbs {
- margin: 0px -20px 5px -6px;
-}
-
-.sws {
- width: 220px;
-}
-
-.sis {
- width: 210px !important;
-}
-
.hd {
display: var(--bhd);
}
@@ -569,35 +572,43 @@ input[type=range]::-moz-range-thumb {
margin: 10px 4px;
width: 230px;
font-size: 19px;
- background-color: var(--c-3);
color: var(--c-d);
cursor: pointer;
- border: 1px solid var(--c-3);
border-radius: 25px;
- transition-duration: 0.5s;
+ transition-duration: 0.3s;
-webkit-backface-visibility: hidden;
-webkit-transform:translate3d(0,0,0);
overflow: clip;
text-overflow: clip;
}
-
+.btn:hover {
+ border: 1px solid var(--c-4);
+ background-color: var(--c-4);
+}
+.btn {
+ border: 1px solid var(--c-3);
+ background-color: var(--c-3);
+}
.btn-s {
width: 276px;
- background-color: var(--c-2);
-}
-.btn-i {
- padding-bottom: 4px;
+ margin: 0 0 10px;
}
+.btn-i {}
.btn-icon {
- margin: 0px 8px 4px 0;
+ margin: -4px 8px 0 0;
vertical-align: middle;
+ display: inline-block;
}
.btna-icon {
margin: 0px;
}
+.btn-n {
+ width: 230px;
+ margin-right: 8px;
+}
.btn-p {
- width: 165px;
- margin: 5px;
+ width: 120px;
+ margin: 5px 0;
}
.btn-xs, .btn-pl-del, .btn-pl-add {
width: 42px;
@@ -650,7 +661,7 @@ input[type=range]::-moz-range-thumb {
width: 42px;
}
.sel-pl {
- width: 165px;
+ width: 100%;
background-position: 141px 16px;
}
.sel-ple {
@@ -677,10 +688,14 @@ input[type=number], input[type=text] {
appearance: textfield;
}
+input[type=number] {
+ text-align: right;
+}
+
textarea {
background: var(--c-2);
color: var(--c-f);
- width: 236px;
+ width: 95%;
height: 90px;
border-radius: 5px;
border: 2px solid #555;
@@ -699,8 +714,8 @@ input[type=text] {
}
.ptxt {
- width: 216px !important;
- margin: 6px !important;
+ width: 240px !important;
+ margin: 0 4px 4px !important;
}
.stxt {
@@ -716,61 +731,25 @@ input[type=number]::-webkit-outer-spin-button {
-webkit-appearance: none;
}
-.pln {
- border-radius: 25px !important;
- width: 67px !important;
- margin: 0 2px 8px 0 !important;
- text-align: center;
-}
-.plnl {
- width: 86px;
- margin: 0 2px 0 0;
- display: inline-block;
-}
-.pli {
- width: 38px;
- margin: 0 0 0 29px;
- display: inline-block;
-}
-
-.segn {
- margin: 3px 0 6px 0 !important;
- text-align: right;
-}
-
.segname, .pname {
- position: absolute;
- top: 0px;
- left: 50%;
- padding: 10px 0;
- transform: translateX(-50%);
+ padding: 4px 0;
white-space: nowrap;
cursor: pointer;
text-align: center;
-}
-/*
-.segname {
- padding: 7px 0;
- font-size: 11px;
-}
-*/
-.pname {
- width: 208px;
overflow: hidden;
text-overflow: clip;
}
.segpwr {
- margin: 8px 0 0;
+ padding: 4px 0 4px 8px;
}
.pid {
position: absolute;
top: 0px;
left: 0px;
- padding: 11px 0px 0px 11px;
+ padding: 12px 0px 0px 12px;
font-size: 16px;
- width: 20px;
text-align: center;
color: var(--c-b);
}
@@ -796,25 +775,6 @@ input[type=number]::-webkit-outer-spin-button {
height: 40px;
}
-.cnf {
- color: var(--c-f);
- cursor: pointer;
- background: var(--c-3);
- border-radius: 25px;
- padding: 8px;
- margin: -8px 0 0;
-}
-
-.cnf-s {
-/*
- padding: 8px;
- position: absolute;
- top: 173px;
- right: 23px;
- padding: 7px 22px;
-*/
-}
-
.pwr {
color: var(--c-6);
transform: translate(2px, 3px);
@@ -848,17 +808,11 @@ input[type=number]::-webkit-outer-spin-button {
}
.revchkl {
- padding: 2px 0px 0px 35px;
+ padding: 4px 0px 0px 35px;
margin-bottom: 0px;
margin-top: 8px;
}
-.fxchkl {
- position: absolute;
- top: 0px;
- left: 8px;
-}
-
.check input, .radio input {
position: absolute;
opacity: 0;
@@ -869,39 +823,25 @@ input[type=number]::-webkit-outer-spin-button {
.checkmark, .radiomark {
position: absolute;
+ top: 0;
bottom: 0;
left: 0;
background-color: var(--c-3);
+ border: 1px solid var(--c-2);
}
.radiomark {
height: 24px;
width: 24px;
border-radius: 50%;
- top: -2px;
}
.checkmark {
height: 25px;
width: 25px;
border-radius: 10px;
- top: 0;
}
-.psv {
- left: initial;
- bottom: initial;
- top: 0;
- right: 0;
-}
-
-.psvl {
- padding: 2px 35px 10px 0px;
- margin-top: 10px;
- margin-bottom: 0px;
-}
-
-
.check:hover input ~ .checkmark {
background-color: var(--c-4);
}
@@ -952,13 +892,11 @@ input[type=number]::-webkit-outer-spin-button {
margin-bottom: 5px;
}
-.seg {
+.seg, .pres {
position: relative;
- display: inline-block;
- padding: 8px;
- margin: 3px 0;
- width: 260px;
- font-size: 19px;
+ display: block;
+ padding: 8px 0;
+ margin: 0 0 10px;
background-color: var(--c-2);
color: var(--c-f);
border: 0px solid var(--c-f);
@@ -966,17 +904,37 @@ input[type=number]::-webkit-outer-spin-button {
text-align: left;
transition: background-color 0.5s;
filter: brightness(1);
+ font-size: 19px;
}
+.seg:last-child {
+ margin: 0;
+}
+
+.seg .schkl {
+ position: absolute;
+ top: 8px;
+ left: 8px;
+}
+
+.pres {
+ padding-bottom: 4px;
+}
+
+#pcont .pres:hover {
+ background-color: var(--c-3);
+}
+
.list {
position: relative;
- width: 260px;
+ width: 280px;
transition: background-color 0.5s;
margin: auto auto 20px;
+ font-size: 19px;
+ line-height: 24px;
}
.lstI {
- border-bottom: 1px solid var(--c-3);
display: flex;
align-items: center;
padding: 8px 10px;
@@ -984,39 +942,36 @@ input[type=number]::-webkit-outer-spin-button {
background-color: var(--c-2);
overflow: hidden;
position: sticky;
+ border: 1px solid var(--c-2);
+ border-radius: 25px;
+ margin: 10px auto 0;
+ min-height: 24px;
}
.lstI:hover {
background: var(--c-4);
}
-.lstI:last-child {
- border: none;
- border-radius: 0 0 20px 20px;
- padding-bottom: 10px;
-}
-
.lstI.selected {
background: var(--c-5);
- top: 135px;
+ top: 142px;
bottom: 0;
}
+.lstI.sticky {
+ top: 100px;
+}
+
.lstI.sticky, .lstI.selected {
z-index: 1;
}
#pallist .lstI.selected {
- top: 80px;
- bottom: 0;
+ top: 84px;
}
#pallist .lstI.sticky {
- top: 40px;
-}
-
-.lstI.sticky {
- top: 98px;
+ top: 42px;
}
.lstIcontent {
@@ -1024,20 +979,20 @@ input[type=number]::-webkit-outer-spin-button {
vertical-align: middle;
padding: 0 20px 0 5px;
text-align: left;
+ display: inline-block;
+ position: relative;
}
.lstIname {
- margin: 3px 0;
white-space: nowrap;
- cursor: pointer;
}
.lstIprev {
- border: 1px solid var(--c-4);
- border-radius: 4px;
width: 100%;
height: 8px;
- margin: auto;
+ position: absolute;
+ bottom: 0;
+ left: 0;
}
.fndIcn { /* needed for magnifier SVG, can be removed when magnifier is in Wicons font */
@@ -1049,6 +1004,11 @@ input[type=number]::-webkit-outer-spin-button {
margin-top: 1px;
}
+.fnd {
+ width: 280px;
+ margin: 0 auto;
+}
+
div.fnd div {
position: absolute;
top: 10px;
@@ -1065,30 +1025,27 @@ div.fnd span {
input[type="text"].fnd {
display: block;
- width: 260px;
+ width: 100%;
box-sizing: border-box;
padding: 8px 48px 8px 60px;
margin: 5px auto 0;
text-align: left;
- border-radius: 20px 20px 0 0;
+ border-radius: 25px;
background: var(--c-2);
- border-bottom: 1px solid var(--c-3);
+ border: 1px solid var(--c-3);
}
input[type="text"].fnd:focus {
- background-color: var(--c-5);
-}
-
-input[type="text"].fnd:not(:placeholder-shown) {
background-color: var(--c-4);
}
-.pres {
- margin-bottom: 6px;
+input[type="text"].fnd:not(:placeholder-shown),
+input[type="text"].fnd:hover {
+ background-color: var(--c-3);
}
.segin {
- padding: 4px 8px 4px 8px;
+ padding: 0 8px 8px;
display: none;
}
diff --git a/wled00/data/index.htm b/wled00/data/index.htm
index 7c592e68f..49bbac5aa 100644
--- a/wled00/data/index.htm
+++ b/wled00/data/index.htm
@@ -24,7 +24,7 @@
Sync
Peek
Info
- Nodes
+ Nodes
Config
PC Mode
@@ -164,18 +164,18 @@
-
-
Loading...
+
+
Reset segments
Transition: s
-
+
@@ -190,7 +190,7 @@
Colors
Effects
Segments
-
Favorites
+
Presets
diff --git a/wled00/data/index.js b/wled00/data/index.js
index 4d8f18685..67b1e544f 100644
--- a/wled00/data/index.js
+++ b/wled00/data/index.js
@@ -512,18 +512,11 @@ function populateQL()
{
var cn = "";
if (pQL.length > 0) {
+ pQL.sort((a,b) => (a[0]>b[0]));
cn += `
Quick load
`;
-
- var it = 0;
for (var key of (pQL||[])) {
- cn += `
${key[1]} `;
- it++;
- if (it > 4) {
- it = 0;
- cn += '
';
- }
+ cn += `
${key[1]} `;
}
- if (it != 0) cn+= '
';
}
gId('pql').innerHTML = cn;
}
@@ -544,15 +537,15 @@ function populatePresets(fromls)
if (!isObject(key[1])) continue;
let i = parseInt(key[0]);
var qll = key[1].ql;
- if (qll) pQL.push([i, qll]);
+ if (qll) pQL.push([i, qll, pName(i)]);
is.push(i);
- cn += `
`;
+ cn += `
`;
if (cfg.comp.pid) cn += `
${i}
`;
cn += `
${isPlaylist(i)?" ":""}${pName(i)}
-
`;
+
`;
pNum++;
}
@@ -667,7 +660,7 @@ function populateSegments(s)
if (i > lSeg) lSeg = i;
cn += `
`;
}
gId('segcont').innerHTML = cn;
@@ -942,8 +933,8 @@ function generateListItemHtml(listName, id, name, clickAction, extraHtml = '')
${name}
- ${extraHtml}
+ ${extraHtml}
`;
}
@@ -1000,13 +991,13 @@ function updateLen(s)
function updatePA(scrollto=false)
{
- var ps = gEBCN("seg");
+ var ps = gEBCN("pres");
for (let i = 0; i < ps.length; i++) {
- ps[i].style.backgroundColor = "var(--c-2)";
+ ps[i].style.backgroundColor = "";
}
ps = gEBCN("psts");
for (let i = 0; i < ps.length; i++) {
- ps[i].style.backgroundColor = "var(--c-2)";
+ ps[i].style.backgroundColor = "";
}
if (currentPreset > 0) {
var acv = gId(`p${currentPreset}o`);
@@ -1083,7 +1074,9 @@ function displayRover(i,s)
function cmpP(a, b)
{
if (!a[1].n) return (a[0] > b[0]);
- return a[1].n.localeCompare(b[1].n,undefined, {numeric: true});
+ // playlists follow presets
+ var name = (a[1].playlist ? '~' : ' ') + a[1].n;
+ return name.localeCompare((b[1].playlist ? '~' : ' ') + b[1].n, undefined, {numeric: true});
}
function makeWS() {
@@ -1145,6 +1138,7 @@ function readState(s,command=false)
if (s.pl<0) currentPreset = s.ps;
else currentPreset = s.pl;
gId('tt').value = s.transition/10;
+ if (s.tdd >= 0) tr = s.tdd/10;
var selc=0; var ind=0;
populateSegments(s);
@@ -1229,7 +1223,7 @@ function requestJson(command=null)
command.v = true; // force complete /json/si API response
command.time = Math.floor(Date.now() / 1000);
var t = d.getElementById('tt');
- if (t.validity.valid) {
+ if (command.transition===null && t.validity.valid) {
var tn = parseInt(t.value*10);
if (tn != tr) command.transition = tn;
}
@@ -1336,9 +1330,13 @@ function makeSeg()
var pend = parseInt(gId(`seg${lowestUnused -1}e`).value,10) + (cfg.comp.seglen?parseInt(gId(`seg${lowestUnused -1}s`).value,10):0);
if (pend < ledCount) ns = pend;
}
+ gId('segutil').scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ });
var cn = `
-
+
${ledCount - ns} LEDs
@@ -1359,7 +1357,7 @@ function makeSeg()
function resetUtil()
{
- gId('segutil').innerHTML = '
Add segment';
+ gId('segutil').innerHTML = '
Add segment';
for (var i=0; i
`;
plEDiv.innerHTML = content;
var dels = plEDiv.getElementsByClassName("btn-pl-del");
- if (dels.length < 2 && p > 0) dels[0].style.display = "none";
+// if (dels.length < 2 && p > 0) dels[0].style.display = "none";
+ if (dels.length < 2) dels[0].style.display = "none";
var sels = gId(`seg${p+100}`).getElementsByClassName("sel");
for (var i of sels) {
@@ -1412,7 +1411,8 @@ function addPl(p,i) {
}
function delPl(p,i) {
- if (plJson[p].ps.length < 2) {if (p == 0) resetPUtil(); return;}
+// if (plJson[p].ps.length < 2) {if (p == 0) resetPUtil(); return;}
+ if (plJson[p].ps.length < 2) return;
plJson[p].ps.splice(i,1);
plJson[p].dur.splice(i,1);
plJson[p].transition.splice(i,1);
@@ -1482,7 +1482,7 @@ ${plSelContent}
`;
- return `
+ return `
Quick load label:
(leave empty for no Quick load button)
@@ -1490,7 +1490,7 @@ ${plSelContent}
${pl?"Show playlist editor":(i>0)?"Overwrite with state":"Use current state"}
-
+
API command
@@ -1502,7 +1502,7 @@ ${plSelContent}
Save to ID 0)?i:getLowestUnusedP()}>
Save
- ${(i>0)?' Delete':'Cancel'}
+ ${(i>0)?' Delete':'Cancel'}
${(i>0)? ('
ID ' +i+ '
'):""}`;
@@ -1510,21 +1510,26 @@ ${(i>0)? ('
ID ' +i+ '
'):""}`;
function makePUtil()
{
- gId('putil').innerHTML = `
`;
+ gId('putil').classList.remove("staytop");
+ gId('putil').scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ });
+ gId('putil').innerHTML = `
`;
for (var i=0; i
-
+
`;
@@ -1546,14 +1551,20 @@ function makePlUtil()
showToast("You need at least 2 presets to make a playlist!"); //return;
}
if (plJson[0].transition[0] < 0) plJson[0].transition[0] = tr;
- gId('putil').innerHTML = ``;
+ gId('putil').classList.remove("staytop");
+ gId('putil').scrollIntoView({
+ behavior: 'smooth',
+ block: 'start',
+ });
+ gId('putil').innerHTML = ``;
refreshPlE(0);
}
function resetPUtil()
{
- var cn = ` New preset `+
- ` `;
+ gId('putil').classList.add("staytop");
+ var cn = ` New preset `+
+ ` `;
gId('putil').innerHTML = cn;
}
@@ -1753,6 +1764,7 @@ function saveP(i,pl)
}
populatePresets();
resetPUtil();
+ if (i>0) expand(pI+100); // collapse edited preset or expand created preset.
}
function testPl(i,bt) {
@@ -1782,7 +1794,7 @@ function delP(i) {
populatePresets();
} else {
bt.style.color = "#f00";
- bt.innerHTML = " Confirm delete";
+ bt.innerHTML = " Delete!";
bt.dataset.cnf = 1;
}
}
@@ -2033,8 +2045,8 @@ function expand(i,a)
seg.style.display = (expanded[i]) ? "block":"none";
gId('sege' +i).style.transform = (expanded[i]) ? "rotate(180deg)":"rotate(0deg)";
- if (expanded[i]) gId(i<100?'segutil':'putil').classList.remove("staytop");
- else gId(i<100?'segutil':'putil').classList.add("staytop");
+ if (expanded[i]) gId(i<100?'segutil':'putil').classList.remove(i<100?"staybot":"staytop");
+ else gId(i<100?'segutil':'putil').classList.add(i<100?"staybot":"staytop");
if (i >= 100) {
var p = i-100;
diff --git a/wled00/data/settings_ui.htm b/wled00/data/settings_ui.htm
index cf63fb790..9c72c3aca 100644
--- a/wled00/data/settings_ui.htm
+++ b/wled00/data/settings_ui.htm
@@ -6,7 +6,7 @@
UI Settings
-
+
@@ -216,6 +217,7 @@
Web Setup
Server description:
Sync button toggles both send and receive:
+ Enable simplified UI:
The following UI customization settings are unique both to the WLED device and this browser.
You will need to set them again if using a different browser, device or WLED IP address.
Refresh the main UI to apply changes.
diff --git a/wled00/data/simple.css b/wled00/data/simple.css
new file mode 100644
index 000000000..08a627b3e
--- /dev/null
+++ b/wled00/data/simple.css
@@ -0,0 +1,758 @@
+@font-face {
+ font-family: "WIcons";
+ src: url(data:application/x-font-woff;charset=utf-8;base64,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) format('woff');
+}
+
+:root {
+ --c-1: #111;
+ --c-f: #fff;
+ --c-2: #222;
+ --c-3: #333;
+ --c-4: #444;
+ --c-5: #555;
+ --c-6: #666;
+ --c-8: #888;
+ --c-b: #bbb;
+ --c-c: #ccc;
+ --c-e: #eee;
+ --c-d: #ddd;
+ --c-r: #831;
+ --t-b: 0.5;
+ --c-o: rgba(34, 34, 34, 0.9);
+ --c-tb : rgba(34, 34, 34, var(--t-b));
+ --c-tba: rgba(102, 102, 102, var(--t-b));
+ --c-tbh: rgba(51, 51, 51, var(--t-b));
+ /*following are internal*/
+ --th: 70px;
+ --tp: 70px;
+ --bh: 63px;
+ --tbp: 14px 8px 10px;
+ --bbp: 9px 0 7px 0;
+ --bhd: none;
+ --bmt: 0px;
+}
+
+html {
+ touch-action: manipulation;
+}
+
+body {
+ margin: 0;
+ background-color: var(--c-1);
+ font-family: Helvetica, Verdana, sans-serif;
+ font-size: 17px;
+ color: var(--c-f);
+ text-align: center;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ -webkit-tap-highlight-color: transparent;
+ scrollbar-width: 6px;
+ scrollbar-color: var(--c-sb) transparent;
+}
+
+html,
+body {
+ height: 100%;
+ width: 100%;
+ position: fixed;
+ overscroll-behavior: none;
+}
+
+#bg {
+ height: 100vh;
+ width: 100vw;
+ position: fixed;
+ z-index: -10;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+ opacity: 0;
+ transition: opacity 2s;
+}
+
+p {
+ margin: 10px 0 2px 0;
+ color: var(--c-d);
+}
+
+button {
+ outline: none;
+ cursor: pointer;
+ background-color: transparent;
+ border: none;
+ transition: color 0.3s, background-color 0.3s;
+ font-size: 19px;
+ color: var(--c-c);
+ min-width: 40px;
+ min-height: 40px;
+}
+button:hover {
+ background: var(--c-4);
+}
+
+.labels {
+ margin: 0;
+ padding: 8px 0 2px 0;
+}
+
+#namelabel {
+ position: fixed;
+ bottom: calc(var(--bh) + 6px);
+ right: 4px;
+ color: var(--c-6);
+ cursor: pointer;
+ writing-mode: vertical-rl;
+}
+
+.wrapper {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ background: var(--c-tb);
+ z-index: 1;
+}
+
+.center {
+ margin: 10px auto 0;
+ width: 320px;
+}
+
+.icons {
+ font-family: 'WIcons';
+ font-style: normal;
+ font-size: 24px;
+ line-height: 1;
+ display: inline-block;
+ margin: -2px 0 4px 0;
+}
+
+.huge {
+ font-size: 42px;
+}
+
+.infot {
+ table-layout: fixed;
+ width: 100%;
+}
+
+.keytd {
+ text-align: left;
+ padding-bottom: 8px;
+}
+
+.valtd {
+ text-align: right;
+ padding-bottom: 8px;
+}
+
+.valtd i {
+ font-size: small;
+}
+
+.slider-icon
+{
+ transform: translate(6px,3px);
+ color: var(--c-d);
+}
+
+.il {
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.tab {
+ background-color: transparent;
+ color: var(--c-d);
+}
+
+.tab button {
+ background-color: transparent;
+ float: left;
+ border: none;
+ transition: color 0.3s, background-color 0.3s;
+ font-size: 17px;
+ color: var(--c-c);
+ min-width: 44px;
+}
+
+.top button {
+ padding: var(--tbp);
+ margin: 0;
+}
+
+.tab button:hover {
+ background-color: var(--c-tbh);
+ color: var(--c-e);
+}
+
+.tab button.active {
+ background-color: var(--c-tba) !important;
+ color: var(--c-f);
+}
+
+.active {
+ background-color: var(--c-6) !important;
+ color: var(--c-f);
+}
+
+.container {
+ width: 100%;
+ height: calc(100% - var(--tp) - var(--bh));
+ margin-top: var(--tp);
+ overscroll-behavior: none;
+}
+
+.tabcontent {
+ position: relative;
+ width: 100%;
+ box-sizing: border-box;
+ border: 0px;
+ overflow: auto;
+ height: 100%;
+ overscroll-behavior: none;
+}
+
+.smooth { transition: transform calc(var(--f, 1)*.5s) ease-out }
+
+.tab-label {
+ margin: 0 0 -5px 0;
+ padding-bottom: 4px;
+}
+
+.overlay {
+ position: fixed;
+ height: 100%;
+ width: 100%;
+ top: 0;
+ left: 0;
+ background-color: var(--c-3);
+ font-size: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 11;
+ opacity: 0.95;
+ transition: 0.7s;
+ pointer-events: none;
+}
+
+#toast {
+ opacity: 0;
+ background-color: var(--c-5);
+ max-width: 90%;
+ color: var(--c-f);
+ text-align: center;
+ border-radius: 5px;
+ padding: 16px;
+ position: fixed;
+ z-index: 5;
+ left: 50%;
+ transform: translateX(-50%);
+ bottom: calc(var(--bh) + 22px);
+ font-size: 17px;
+ pointer-events: none;
+}
+
+#toast.show {
+ opacity: 1;
+ animation: fadein 0.5s, fadein 0.5s 2.5s reverse;
+}
+
+#toast.error {
+ opacity: 1;
+ background-color: #b21;
+ animation: fadein 0.5s;
+}
+
+.modal {
+ position:fixed;
+ left: 0px;
+ bottom: 0px;
+ right: 0px;
+ top: calc(var(--th) - 1px);
+ background-color: var(--c-o);
+ transform: translateY(100%);
+ transition: transform 0.4s;
+ padding: 8px;
+ font-size: 20px;
+ overflow: auto;
+}
+
+#info {
+ z-index: 3;
+}
+
+#rover, #nodes {
+ z-index: 2;
+}
+
+#ndlt {
+ margin: 12px 0;
+}
+
+#roverstar {
+ position: fixed;
+ top: calc(var(--th) + 5px);
+ left: 1px;
+ display: none;
+ cursor: pointer;
+}
+
+#connind {
+ position: fixed;
+ bottom: calc(var(--bh) + 5px);
+ left: 4px;
+ padding: 5px;
+ border-radius: 5px;
+ background-color: #a90;
+ z-index: -2;
+}
+
+#imgw {
+ display: inline-block;
+ margin: 8px;
+}
+
+#kv, #kn {
+ max-width: 490px;
+ display: inline-block;
+}
+
+#kn td {
+ padding-bottom: 12px;
+}
+
+#heart {
+ transition: color 0.9s;
+ font-size: 16px;
+ color: #f00;
+}
+
+img {
+ max-width: 100%;
+ max-height: 100%;
+}
+
+.wi {
+ image-rendering: pixelated;
+ image-rendering: crisp-edges;
+ width: 210px;
+}
+
+@keyframes fadein {
+ from {bottom: 0; opacity: 0;}
+ to {bottom: calc(var(--bh) + 22px); opacity: 1;}
+}
+
+.sliderwrap {
+ height: 30px;
+ width: 240px;
+ position: relative;
+}
+
+.sliderdisplay {
+ content:'';
+ position: absolute;
+ top: 13px; bottom: 13px;
+ left: 10px; right: 10px;
+ background: var(--c-4);
+ border-radius: 17px;
+ pointer-events: none;
+ z-index: -1;
+}
+
+.sliderbubble {
+ width: 24px;
+ position: relative;
+ display: inline-block;
+ border-radius: 10px;
+ background: var(--c-3);
+ color: var(--c-f);
+ padding: 4px 4px 2px;
+ font-size: 14px;
+ right: 5px;
+ transition: visibility 0.25s ease, opacity 0.25s ease;
+ opacity: 0;
+ visibility: hidden;
+}
+
+output.sliderbubbleshow {
+ visibility: visible;
+ opacity: 1;
+}
+
+.hidden {
+ display: none;
+}
+
+input[type=range] {
+ -webkit-appearance: none;
+ width: 220px;
+ padding: 0px;
+ margin: 0px 10px 0px 10px;
+ background-color: transparent;
+ cursor: pointer;
+}
+input[type=range]:focus {
+ outline: none;
+}
+input[type=range]::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 30px;
+ cursor: pointer;
+ background: transparent;
+}
+input[type=range]::-webkit-slider-thumb {
+ height: 16px;
+ width: 16px;
+ border-radius: 17px;
+ background: var(--c-f);
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: 7px;
+}
+input[type=range]::-moz-range-track {
+ width: 100%;
+ height: 30px;
+ background-color: rgba(0, 0, 0, 0);
+}
+input[type=range]::-moz-range-thumb {
+ border: 0px solid rgba(0, 0, 0, 0);
+ height: 16px;
+ width: 16px;
+ border-radius: 17px;
+ background: var(--c-f);
+ transform: translateY(7px);
+}
+#wwrap {
+ display: none;
+}
+
+.hd {
+ display: var(--bhd);
+}
+
+#briwrap {
+ float: right;
+ margin-top: var(--bmt);
+}
+
+#picker {
+ margin: 10px auto;
+ width: 260px;
+}
+
+#rgbwrap {
+ display: none;
+}
+
+.btn {
+ margin: 10px auto 0;
+ width: 280px;
+ font-size: 19px;
+ background-color: var(--c-3);
+ color: var(--c-d);
+ cursor: pointer;
+ border: 1px solid var(--c-3);
+ border-radius: 25px;
+ transition-duration: 0.3s;
+ -webkit-backface-visibility: hidden;
+ -webkit-transform:translate3d(0,0,0);
+ overflow: clip;
+ text-overflow: clip;
+ min-height: 40px;
+ line-height: 40px;
+}
+.btn:hover {
+ background-color: var(--c-4);
+ border: 1px solid var(--c-4);
+}
+
+#fxBtn, #palBtn {
+ background-color: var(--c-2);
+ border: 1px solid var(--c-2);
+}
+#fxBtn:hover, #palBtn:hover {
+ background-color: var(--c-3);
+ border: 1px solid var(--c-3);
+}
+
+.btn-icon {
+ margin-right: 8px;
+ vertical-align: middle;
+ display: inline-block;
+}
+
+.btna-icon {
+ margin: 0px;
+}
+
+.qcs {
+ padding: 14px;
+ margin: 2px;
+ border-radius: 14px;
+ display: inline-block;
+}
+.qcsb {
+ padding: 13px;
+ border: 1px solid var(--c-f);
+}
+option {
+ background-color: var(--c-3);
+ color: var(--c-f);
+}
+input[type=number], input[type=text] {
+ background: var(--c-3);
+ color: var(--c-f);
+ border: 0px solid var(--c-f);
+ border-radius: 5px;
+ padding: 8px;
+ margin: 6px 6px 6px 0;
+ font-size: 19px;
+ transition: background-color 0.2s;
+ outline: none;
+ width: 50px;
+ -webkit-appearance: textfield;
+ -moz-appearance: textfield;
+ appearance: textfield;
+}
+
+::selection {
+ background: var(--c-b);
+}
+
+input[type=number]:focus, input[type=text]:focus {
+ background: var(--c-6);
+}
+
+input[type=number]::-webkit-inner-spin-button,
+input[type=number]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+}
+
+.pid {
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ padding: 12px 0px 0px 12px;
+ font-size: 16px;
+ width: 20px;
+ text-align: center;
+ color: var(--c-b);
+}
+
+.xxs {
+ width: 44px;
+ margin: 6px;
+}
+
+.psts {
+ background-color: var(--c-3);
+ color: var(--c-f);
+ cursor: pointer;
+ padding: 2px 0 0 0;
+ height: 44px;
+}
+
+.pwr {
+ color: var(--c-6);
+ transform: translate(2px, 3px);
+ cursor: pointer;
+}
+
+.act {
+ color: var(--c-f);
+}
+
+.h {
+ font-size: 13px;
+ color: var(--c-b);
+}
+
+.list {
+ position: relative;
+ width: 280px;
+ transition: background-color 0.5s;
+ margin: auto auto 20px;
+ font-size: 19px;
+ line-height: 24px;
+}
+
+.lstI {
+ cursor: pointer;
+ background-color: var(--c-2);
+ overflow: hidden;
+ border-radius: 20px;
+ display: block;
+ position: relative;
+ border: 1px solid var(--c-2);
+ padding: 8px 10px;
+ margin: 10px 0;
+ min-height: 24px;
+}
+
+.lstI:hover {
+ background: var(--c-4);
+}
+/*
+.lstI:last-child {
+ border: none;
+ border-radius: 0 0 20px 20px;
+ padding-bottom: 10px;
+}
+*/
+.lstI.selected {
+ background: var(--c-5);
+}
+
+.lstIcontent {
+ width: 100%;
+ vertical-align: middle;
+ padding: 0 20px 0 5px;
+ text-align: left;
+}
+
+.lstIname {
+ white-space: nowrap;
+ cursor: pointer;
+}
+
+.lstIprev {
+ width: 100%;
+ height: 8px;
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ }
+
+/* Dropdown Content (Hidden by Default) */
+.dd-content {
+ display: none;
+ position: absolute;
+ width: 284px;
+ z-index: 1;
+ height: 266px;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ padding: 0 18px;
+ margin-top: 10px;
+}
+
+.fndIcn { /* needed for magnifier SVG, can be removed when magnifier is in Wicons font */
+ width: 24px;
+ height: 24px;
+ stroke: var(--c-e);
+ stroke-width: 3px;
+ fill-opacity: 0;
+ margin-top: 1px;
+}
+
+.fnd {
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ width: 280px;
+ margin: 0 auto;
+}
+
+div.fnd div {
+ position: absolute;
+ top: 10px;
+ left: 13px;
+ z-index: 1;
+}
+
+div.fnd span {
+ position: absolute;
+ display: none;
+ top: 10px;
+ right: 13px;
+ cursor: pointer;
+ z-index: 1;
+}
+
+input[type="text"].fnd {
+ display: block;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 8px 44px 8px 44px;
+ margin: 5px auto 0;
+ text-align: left;
+ border-radius: 20px;
+ background-color: var(--c-2);
+ border: 1px solid var(--c-4);
+}
+
+input[type="text"].fnd:focus {
+ background-color: var(--c-4);
+}
+
+input[type="text"].fnd:not(:placeholder-shown), input[type="text"].fnd:hover {
+ background-color: var(--c-3);
+}
+
+.h, .c {
+ text-align: center;
+}
+
+::-webkit-scrollbar {
+ width: 6px;
+}
+::-webkit-scrollbar-track {
+ background: transparent;
+}
+::-webkit-scrollbar-thumb {
+ background: var(--c-sb);
+ opacity: 0.2;
+ border-radius: 5px;
+}
+::-webkit-scrollbar-thumb:hover {
+ background: var(--c-sbh);
+}
+
+@media not all and (hover: none) {
+ .sliderwrap:hover + output.sliderbubble {
+ visibility: visible;
+ opacity: 1;
+ }
+}
+
+@media all and (max-width: 335px) {
+ .sliderbubble {
+ display: none;
+ }
+}
+
+@media all and (max-width: 550px) and (min-width: 374px) {
+ .infobtn {
+ width: 160px;
+ }
+}
+
+@media all and (max-width: 540px) {
+ .top button {
+ width: 16.6%;
+ padding: 8px 0 4px 0;
+ }
+}
+
+@media all and (min-width: 541px) and (max-width: 719px) {
+ .top button {
+ width: 14.2%;
+ padding: 8px 0 4px 0;
+ }
+}
+
+@media all and (max-width: 719px) {
+ .hd {
+ display: none !important;
+ }
+ #briwrap {
+ margin-top: 0px !important;
+ float: none;
+ }
+}
diff --git a/wled00/data/simple.htm b/wled00/data/simple.htm
new file mode 100644
index 000000000..b2e3be402
--- /dev/null
+++ b/wled00/data/simple.htm
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+ WLED
+
+
+
+
+
+Loading WLED UI...
+Sorry, WLED UI needs JavaScript!
+
+
+
+
+
+
Power
+
Info
+
Nodes
+
Config
+
Expand
+
Global
+
+
+
+
+
+
+
+
+
+
+
White channel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
R
+
+
+ 1
+ 2
+ 3
+
+
+
+
+
+
+
+
+
+
Effect
+
+
+
+
+
+
Solid
+
Default
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Loading...
+
Refresh
+
Close Info
+
Instance List
+
Reboot WLED
+
Made with ❤︎ by Aircoookie and the WLED community
+
+
+
+
WLED instances
+
Loading...
+
Refresh
+
Close list
+
+
+
+
+
+
+
+
diff --git a/wled00/data/simple.js b/wled00/data/simple.js
new file mode 100644
index 000000000..7cc25a280
--- /dev/null
+++ b/wled00/data/simple.js
@@ -0,0 +1,1383 @@
+//page js
+var loc = false, locip;
+var noNewSegs = false;
+var isOn = false, isInfo = false, isNodes = false, isRgbw = false;
+var whites = [0,0,0];
+var selColors;
+var powered = [true];
+var selectedFx = 0;
+var selectedPal = 0;
+var csel = 0;
+var currentPreset = -1;
+var lastUpdate = 0;
+var segCount = 0, ledCount = 0, lowestUnused = 0, maxSeg = 0, lSeg = 0;
+var lastw = 0;
+var tr = 7;
+var d = document;
+const ranges = RangeTouch.setup('input[type="range"]', {});
+var palettesData;
+var pJson = {}, eJson = {}, lJson = {};
+var pN = "", pI = 0, pNum = 0;
+var pmt = 1, pmtLS = 0, pmtLast = 0;
+var lastinfo = {};
+var ws;
+var cfg = {
+ theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
+ comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, labels:true, pcmbot:false, pid:true, seglen:false}
+};
+var hol = [
+ [0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas
+ [0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day
+ [2022,3,17,2,"https://aircoookie.github.io/easter.png"],
+ [2023,3,9,2,"https://aircoookie.github.io/easter.png"],
+ [2024,2,31,2,"https://aircoookie.github.io/easter.png"]
+];
+
+var cpick = new iro.ColorPicker("#picker", {
+ width: 260,
+ wheelLightness: false,
+ wheelAngle: 90,
+ layout: [
+ {
+ component: iro.ui.Wheel,
+ options: {}
+ },
+ {
+ component: iro.ui.Slider,
+ options: { sliderType: 'value' }
+ },
+ {
+ component: iro.ui.Slider,
+ options: {
+ sliderType: 'kelvin',
+ minTemperature: 2100,
+ maxTemperature: 10000
+ }
+ }
+ ]
+});
+
+function handleVisibilityChange() {if (!d.hidden && new Date () - lastUpdate > 3000) requestJson();}
+function sCol(na, col) {d.documentElement.style.setProperty(na, col);}
+function gId(c) {return d.getElementById(c);}
+function gEBCN(c) {return d.getElementsByClassName(c);}
+function isO(item) { return (item && typeof item === 'object' && !Array.isArray(item)); }
+
+function applyCfg()
+{
+ cTheme(cfg.theme.base === "light");
+ var bg = cfg.theme.color.bg;
+ if (bg) sCol('--c-1', bg);
+ var ccfg = cfg.comp.colors;
+ gId('picker').style.display = "none"; // ccfg.picker ? "block":"none";
+ //gId('rgbwrap').style.display = ccfg.rgb ? "block":"none";
+ gId('qcs-w').style.display = "block"; // ccfg.quick ? "block":"none";
+ var l = cfg.comp.labels; //l = false;
+ var e = d.querySelectorAll('.tab-label');
+ for (var i=0; i {
+ var a = parseFloat(cfg.theme.alpha.bg);
+ if (isNaN(a)) a = 0.6;
+ bg.style.opacity = a;
+ bg.style.backgroundImage = `url(${img.src})`;
+ img = null;
+ });
+}
+
+function loadSkinCSS(cId)
+{
+ if (!gId(cId)) // check if element exists
+ {
+ var h = document.getElementsByTagName('head')[0];
+ var l = document.createElement('link');
+ l.id = cId;
+ l.rel = 'stylesheet';
+ l.type = 'text/css';
+ l.href = (loc?`http://${locip}`:'.') + '/skin.css';
+ l.media = 'all';
+ h.appendChild(l);
+ }
+}
+
+async function onLoad()
+{
+ if (window.location.protocol == "file:") {
+ loc = true;
+ locip = localStorage.getItem('locIp');
+ if (!locip)
+ {
+ locip = prompt("File Mode. Please enter WLED IP!");
+ localStorage.setItem('locIp', locip);
+ }
+ }
+ var sett = localStorage.getItem('wledUiCfg');
+ if (sett) cfg = mergeDeep(cfg, JSON.parse(sett));
+
+ applyCfg();
+ if (cfg.theme.bg.url=="" || cfg.theme.bg.url === "https://picsum.photos/1920/1080") {
+ var iUrl = cfg.theme.bg.url;
+ fetch((loc?`http://${locip}`:'.') + "/holidays.json", {
+ method: 'get'
+ })
+ .then(res => {
+ return res.json();
+ })
+ .then(json => {
+ if (Array.isArray(json)) hol = json;
+ //TODO: do some parsing first
+ })
+ .catch(function(error){
+ console.log("holidays.json does not contain array of holidays. Defaults loaded.");
+ })
+ .finally(()=>{
+ var today = new Date();
+ for (var i=0; i=hs && today{
+ loadPalettesData();
+ loadFX(()=>{
+ loadPresets(()=>{
+ loadInfo(requestJson);
+ });
+ });
+ });
+ updateUI(true);
+
+ d.addEventListener("visibilitychange", handleVisibilityChange, false);
+ size();
+ gId("cv").style.opacity=0;
+ var sls = d.querySelectorAll('input[type="range"]');
+ for (var sl of sls) {
+ sl.addEventListener('touchstart', toggleBubble);
+ sl.addEventListener('touchend', toggleBubble);
+ }
+}
+
+var timeout;
+function showToast(text, error = false)
+{
+ if (error) gId('connind').style.backgroundColor = "#831";
+ var x = gId("toast");
+ x.innerHTML = text;
+ x.className = error ? "error":"show";
+ clearTimeout(timeout);
+ x.style.animation = 'none';
+ timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
+}
+
+function showErrorToast()
+{
+ if (ws && ws.readyState === WebSocket.OPEN) {
+ // if we received a timeout force WS reconnect
+ ws.close();
+ ws = null;
+ if (lastinfo.ws > -1) setTimeout(makeWS,500);
+ }
+ showToast('Connection to light failed!', true);
+}
+
+function clearErrorToast() {gId("toast").className = gId("toast").className.replace("error", "");}
+
+function getRuntimeStr(rt)
+{
+ var t = parseInt(rt);
+ var days = Math.floor(t/86400);
+ var hrs = Math.floor((t - days*86400)/3600);
+ var mins = Math.floor((t - days*86400 - hrs*3600)/60);
+ var str = days ? (days + " " + (days == 1 ? "day" : "days") + ", ") : "";
+ str += (hrs || days) ? (hrs + " " + (hrs == 1 ? "hour" : "hours")) : "";
+ if (!days && hrs) str += ", ";
+ if (t > 59 && !days) str += mins + " min";
+ if (t < 3600 && t > 59) str += ", ";
+ if (t < 3600) str += (t - mins*60) + " sec";
+ return str;
+}
+
+function inforow(key, val, unit = "")
+{
+ return `${key} ${val}${unit} `;
+}
+
+function pName(i)
+{
+ var n = "Preset " + i;
+ if (pJson && pJson[i] && pJson[i].n) n = pJson[i].n;
+ return n;
+}
+
+function isPlaylist(i)
+{
+ return pJson[i].playlist && pJson[i].playlist.ps;
+}
+
+function papiVal(i)
+{
+ if (!pJson || !pJson[i]) return "";
+ var o = Object.assign({},pJson[i]);
+ if (o.win) return o.win;
+ delete o.n; delete o.p; delete o.ql;
+ return JSON.stringify(o);
+}
+
+function qlName(i)
+{
+ if (!pJson || !pJson[i] || !pJson[i].ql) return "";
+ return pJson[i].ql;
+}
+
+function cpBck()
+{
+ var copyText = gId("bck");
+
+ copyText.select();
+ copyText.setSelectionRange(0, 999999);
+ d.execCommand("copy");
+ showToast("Copied to clipboard!");
+}
+
+function loadPresets(callback = null)
+{
+ //1st boot (because there is a callback)
+ if (callback && pmt == pmtLS && pmt > 0) {
+ //we have a copy of the presets in local storage and don't need to fetch another one
+ pJson = JSON.parse(localStorage.getItem("wledP"));
+ populatePresets();
+ pmtLast = pmt;
+ callback();
+ return;
+ }
+
+ //afterwards
+ if (!callback && pmt == pmtLast) return;
+
+ pmtLast = pmt;
+
+ var url = (loc?`http://${locip}`:'') + '/presets.json';
+
+ fetch(url, {
+ method: 'get'
+ })
+ .then(res => {
+ if (!res.ok) showErrorToast();
+ return res.json();
+ })
+ .then(json => {
+ clearErrorToast();
+ pJson = json;
+ populatePresets();
+ })
+ .catch(function (error) {
+ showToast(error, true);
+ console.log(error);
+ })
+ .finally(()=>{
+ if (callback) setTimeout(callback,99);
+ });
+}
+
+function loadPalettes(callback = null)
+{
+ var url = (loc?`http://${locip}`:'') + '/json/palettes';
+
+ fetch(url, {
+ method: 'get'
+ })
+ .then(res => {
+ if (!res.ok) showErrorToast();
+ return res.json();
+ })
+ .then(json => {
+ clearErrorToast();
+ lJson = Object.entries(json);
+ populatePalettes();
+ })
+ .catch(function (error) {
+ showToast(error, true);
+ })
+ .finally(()=>{
+ if (callback) callback();
+ });
+}
+
+function loadFX(callback = null)
+{
+ var url = (loc?`http://${locip}`:'') + '/json/effects';
+
+ fetch(url, {
+ method: 'get'
+ })
+ .then(res => {
+ if (!res.ok) showErrorToast();
+ return res.json();
+ })
+ .then(json => {
+ clearErrorToast();
+ eJson = Object.entries(json);
+ populateEffects();
+ })
+ .catch(function (error) {
+ showToast(error, true);
+ })
+ .finally(()=>{
+ if (callback) callback();
+ });
+}
+
+var pQL = [];
+function populateQL()
+{
+ var cn = "";
+ if (pQL.length > 0) {
+ pQL.sort((a,b) => (a[0]>b[0]));
+ for (var key of (pQL||[])) {
+ cn += `${key[1]} `;
+ }
+ }
+ gId('pql').innerHTML = cn;
+}
+
+function populatePresets()
+{
+ if (!pJson) {pJson={};return};
+ delete pJson["0"];
+ var cn = ""; //`All presets
`;
+ var arr = Object.entries(pJson);
+ arr.sort(cmpP);
+ pQL = [];
+ var is = [];
+ pNum = 0;
+ for (var key of (arr||[]))
+ {
+ if (!isO(key[1])) continue;
+ let i = parseInt(key[0]);
+ var qll = key[1].ql;
+ if (qll) pQL.push([i, qll, pName(i)]);
+ is.push(i);
+
+ cn += ``;
+ //if (cfg.comp.pid) cn += `
${i}
`;
+ cn += `${isPlaylist(i)?"
":""}
${pName(i)} `;
+ pNum++;
+ }
+ gId('pcont').innerHTML = cn;
+ updatePA(true);
+ populateQL();
+}
+
+function loadInfo(callback=null)
+{
+ var url = (loc?`http://${locip}`:'') + '/json/info';
+ fetch(url, {
+ method: 'get'
+ })
+ .then(res => {
+ if (!res.ok) showToast('Could not load Info!', true);
+ return res.json();
+ })
+ .then(json => {
+ clearErrorToast();
+ lastinfo = json;
+ var name = json.name;
+ gId('namelabel').innerHTML = name;
+// if (name === "Dinnerbone") d.documentElement.style.transform = "rotate(180deg)";
+ if (json.live) name = "(Live) " + name;
+ if (loc) name = "(L) " + name;
+ d.title = name;
+ isRgbw = json.leds.wv;
+ ledCount = json.leds.count;
+ syncTglRecv = json.str;
+ maxSeg = json.leds.maxseg;
+ pmt = json.fs.pmt;
+ if (isInfo) populateInfo(json);
+ reqsLegal = true;
+ if (!ws && lastinfo.ws > -1) setTimeout(makeWS,500);
+ })
+ .catch(function (error) {
+ showToast(error, true);
+ console.log(error);
+ })
+ .finally(()=>{
+ if (callback) callback();
+ });
+}
+
+function populateInfo(i)
+{
+ var cn="";
+ var heap = i.freeheap/1000;
+ heap = heap.toFixed(1);
+ var pwr = i.leds.pwr;
+ var pwru = "Not calculated";
+ if (pwr > 1000) {pwr /= 1000; pwr = pwr.toFixed((pwr > 10) ? 0 : 1); pwru = pwr + " A";}
+ else if (pwr > 0) {pwr = 50 * Math.round(pwr/50); pwru = pwr + " mA";}
+ var urows="";
+ if (i.u) {
+ for (const [k, val] of Object.entries(i.u)) {
+ if (val[1])
+ urows += inforow(k,val[0],val[1]);
+ else
+ urows += inforow(k,val);
+ }
+ }
+ var vcn = "Kuuhaku";
+ if (i.ver.startsWith("0.13.")) vcn = "Toki";
+ if (i.ver.includes("-bl")) vcn = "Ryujin";
+ if (i.cn) vcn = i.cn;
+
+ cn += `v${i.ver} "${vcn}"
+${urows}
+${inforow("Build",i.vid)}
+${inforow("Signal strength",i.wifi.signal +"% ("+ i.wifi.rssi, " dBm)")}
+${inforow("Uptime",getRuntimeStr(i.uptime))}
+${inforow("Free heap",heap," kB")}
+${i.psram?inforow("Free PSRAM",(i.psram/1024).toFixed(1)," kB"):""}
+${inforow("Estimated current",pwru)}
+${inforow("Average FPS",i.leds.fps)}
+${inforow("MAC address",i.mac)}
+${inforow("Filesystem",i.fs.u + "/" + i.fs.t + " kB (" +Math.round(i.fs.u*100/i.fs.t) + "%)")}
+${inforow("Environment",i.arch + " " + i.core + " (" + i.lwip + ")")}
+
`;
+ gId('kv').innerHTML = cn;
+}
+
+function populateSegments(s)
+{
+ var cn = "";
+ segCount = (s.seg||[]).length;
+ lowestUnused = 0; lSeg = 0;
+
+ if (segCount > 1) {
+ for (var y = 0; y < segCount && y<4; y++)
+ {
+ var inst=s.seg[y];
+ let i = parseInt(inst.id);
+ powered[i] = inst.on;
+ if (i == lowestUnused) lowestUnused = i+1;
+ if (i > lSeg) lSeg = i;
+
+ cn +=
+`${inst.n && cfg.comp.labels ? ''+inst.n+'
' : ''}
+`;
+ }
+ if (gId('buttonBri').className !== 'active') tglBri(true);
+ } else {
+ tglBri(false);
+ }
+ gId('buttonBri').style.display = (segCount > 1) ? "block" : "none";
+ gId('segcont').innerHTML = cn;
+ for (var i = 0; i < segCount && i<4; i++) updateTrail(gId(`seg${i}bri`));
+}
+
+function btype(b)
+{
+ switch (b) {
+ case 2:
+ case 32: return "ESP32";
+ case 1:
+ case 82: return "ESP8266";
+ }
+ return "?";
+}
+
+function bname(o)
+{
+ if (o.name=="WLED") return o.ip;
+ return o.name;
+}
+
+function populateNodes(i,n)
+{
+ var cn="";
+ var urows="";
+ var nnodes = 0;
+ if (n.nodes) {
+ n.nodes.sort((a,b) => (a.name).localeCompare(b.name));
+ for (var x=0;x${bname(o)}`;
+ urows += inforow(url,`${btype(o.type)}${o.vid==0?"N/A":o.vid} `);
+ nnodes++;
+ }
+ }
+ }
+ if (i.ndc < 0) cn += `Instance List is disabled.`;
+ else if (nnodes == 0) cn += `No other instances found.`;
+ cn += `
+ ${urows}
+ ${inforow("Current instance:",i.name)}
+
`;
+ gId('kn').innerHTML = cn;
+}
+
+function loadNodes()
+{
+ var url = (loc?`http://${locip}`:'') + '/json/nodes';
+ fetch(url, {
+ method: 'get'
+ })
+ .then(res => {
+ if (!res.ok) showToast('Could not load Node list!', true);
+ return res.json();
+ })
+ .then(json => {
+ clearErrorToast();
+ populateNodes(lastinfo, json);
+ })
+ .catch(function (error) {
+ showToast(error, true);
+ console.log(error);
+ });
+}
+
+function populateEffects()
+{
+ var effects = eJson;
+ var html = "";
+
+ effects.shift(); //remove solid
+ for (let i = 0; i < effects.length; i++) effects[i] = {id: effects[i][0], name:effects[i][1]};
+ effects.sort((a,b) => (a.name).localeCompare(b.name));
+ effects.unshift({
+ "id": 0,
+ "name": "Solid",
+ });
+ for (let i = 0; i < effects.length; i++) {
+ html += generateListItemHtml(
+ effects[i].id,
+ effects[i].name,
+ 'setEffect'
+ );
+ }
+ gId('fxlist').innerHTML=html;
+}
+
+function populatePalettes()
+{
+ var palettes = lJson;
+ palettes.shift(); //remove default
+ for (let i = 0; i < palettes.length; i++) {
+ palettes[i] = {
+ "id": palettes[i][0],
+ "name": palettes[i][1]
+ };
+ }
+ palettes.sort((a,b) => (a.name).localeCompare(b.name));
+ palettes.unshift({
+ "id": 0,
+ "name": "Default",
+ });
+ var html = "";
+ for (let i = 0; i < palettes.length; i++) {
+ html += generateListItemHtml(
+ palettes[i].id,
+ palettes[i].name,
+ 'setPalette',
+ `
`
+ );
+ }
+ gId('pallist').innerHTML=html;
+}
+
+function redrawPalPrev()
+{
+ let palettes = d.querySelectorAll('#pallist .lstI');
+ for (let i = 0; i < palettes.length; i++) {
+ let id = palettes[i].dataset.id;
+ let lstPrev = palettes[i].querySelector('.lstIprev');
+ if (lstPrev) {
+ lstPrev.style = genPalPrevCss(id);
+ }
+ }
+}
+
+function genPalPrevCss(id)
+{
+ if (!palettesData) return;
+
+ var paletteData = palettesData[id];
+ var previewCss = "";
+
+ if (!paletteData) return 'display: none';
+
+ // We need at least two colors for a gradient
+ if (paletteData.length == 1) {
+ paletteData[1] = paletteData[0];
+ if (Array.isArray(paletteData[1])) {
+ paletteData[1][0] = 255;
+ }
+ }
+
+ var gradient = [];
+ for (let j = 0; j < paletteData.length; j++) {
+ const element = paletteData[j];
+ let r;
+ let g;
+ let b;
+ let index = false;
+ if (Array.isArray(element)) {
+ index = element[0]/255*100;
+ r = element[1];
+ g = element[2];
+ b = element[3];
+ } else if (element == 'r') {
+ r = Math.random() * 255;
+ g = Math.random() * 255;
+ b = Math.random() * 255;
+ } else {
+ if (selColors) {
+ let e = element[1] - 1;
+ if (Array.isArray(selColors[e])) {
+ r = selColors[e][0];
+ g = selColors[e][1];
+ b = selColors[e][2];
+ } else {
+ r = (selColors[e]>>16) & 0xFF;
+ g = (selColors[e]>> 8) & 0xFF;
+ b = (selColors[e] ) & 0xFF;
+ }
+ }
+ }
+ if (index === false) {
+ index = j / paletteData.length * 100;
+ }
+
+ gradient.push(`rgb(${r},${g},${b}) ${index}%`);
+ }
+
+ return `background: linear-gradient(to right,${gradient.join()});`;
+}
+
+function generateOptionItemHtml(id, name)
+{
+ return `${name} `;
+}
+
+function generateListItemHtml(id, name, clickAction, extraHtml = '')
+{
+ return `${name} ${extraHtml}
`;
+}
+
+function updateTrail(e, slidercol)
+{
+ if (e==null) return;
+ var max = e.hasAttribute('max') ? e.attributes.max.value : 255;
+ var perc = e.value * 100 / max;
+ perc = parseInt(perc);
+ if (perc < 50) perc += 2;
+ var scol;
+ switch (slidercol) {
+ case 1: scol = "#f00"; break;
+ case 2: scol = "#0f0"; break;
+ case 3: scol = "#00f"; break;
+ default: scol = "var(--c-f)";
+ }
+ var val = `linear-gradient(90deg, ${scol} ${perc}%, var(--c-4) ${perc}%)`;
+ e.parentNode.getElementsByClassName('sliderdisplay')[0].style.background = val;
+ var bubble = e.parentNode.parentNode.getElementsByTagName('output')[0];
+ if (bubble) bubble.innerHTML = e.value;
+}
+
+function toggleBubble(e)
+{
+ var bubble = e.target.parentNode.parentNode.getElementsByTagName('output')[0];
+ bubble.classList.toggle('sliderbubbleshow');
+}
+
+function updatePA(scrollto=false)
+{
+ var ps = gEBCN("psts"); // quick load
+ for (let i = 0; i < ps.length; i++) {
+ ps[i].style.backgroundColor = "var(--c-2)";
+ }
+ if (currentPreset > 0) {
+ var acv = gId(`p${currentPreset}o`);
+ if (acv) {
+ acv.style.background = "var(--c-6)";
+ }
+ acv = gId(`p${currentPreset}qlb`);
+ if (acv) acv.style.background = "var(--c-6)";
+ }
+}
+
+function updateUI(scrollto=false)
+{
+ gId('buttonPower').className = (isOn) ? "active":"";
+
+ var sel = 0;
+ if (lJson && lJson.length) {
+ for (var i=0; i ' + lJson[sel].name;
+ }
+ sel = 0;
+ if (eJson && eJson.length) {
+ for (var i=0; i ' + eJson[sel].name;
+ }
+
+ updateTrail(gId('sliderBri'));
+ updateTrail(gId('sliderSpeed'));
+ updateTrail(gId('sliderIntensity'));
+ updateTrail(gId('sliderW'));
+ if (isRgbw) gId('wwrap').style.display = "block";
+
+ updatePA(scrollto);
+ redrawPalPrev();
+
+ var l = cfg.comp.labels; //l = false;
+ var e = d.querySelectorAll('.labels');
+ for (var i=0; i b[0]);
+ // playlists follow presets
+ var name = (a[1].playlist ? '~' : ' ') + a[1].n;
+ return name.localeCompare((b[1].playlist ? '~' : ' ') + b[1].n, undefined, {numeric: true});
+}
+
+function makeWS() {
+ if (ws || lastinfo.ws<0) return;
+ ws = new WebSocket('ws://'+(loc?locip:window.location.hostname)+'/ws');
+ ws.onmessage = function(event) {
+ var json = JSON.parse(event.data);
+ if (json.leds) return; //liveview packet
+ clearTimeout(jsonTimeout);
+ jsonTimeout = null;
+ lastUpdate = new Date();
+ clearErrorToast();
+ gId('connind').style.backgroundColor = "#079";
+ // json object should contain json.info AND json.state (but may not)
+ var info = json.info;
+ if (info) {
+ var name = info.name;
+ lastinfo = info;
+ gId('namelabel').innerHTML = name;
+ //if (name === "Dinnerbone") d.documentElement.style.transform = "rotate(180deg)";
+ if (info.live) name = "(Live) " + name;
+ if (loc) name = "(L) " + name;
+ d.title = name;
+ isRgbw = info.leds.wv;
+ ledCount = info.leds.count;
+ syncTglRecv = info.str;
+ maxSeg = info.leds.maxseg;
+ pmt = info.fs.pmt;
+ if (isInfo) populateInfo(info);
+ } else
+ info = lastinfo;
+ var s = json.state ? json.state : json;
+ readState(s);
+ };
+ ws.onclose = function(event) {
+ gId('connind').style.backgroundColor = "#831";
+ ws = null;
+ }
+ ws.onopen = function(event) {
+ ws.send("{'v':true}");
+ reqsLegal = true;
+ clearErrorToast();
+ }
+}
+
+function readState(s,command=false)
+{
+ if (!s) return false;
+
+ isOn = s.on;
+ gId('sliderBri').value= s.bri;
+ nlA = s.nl.on;
+ nlDur = s.nl.dur;
+ nlTar = s.nl.tbri;
+ nlFade = s.nl.fade;
+ syncSend = s.udpn.send;
+ if (s.pl<0) currentPreset = s.ps;
+ else currentPreset = s.pl;
+ tr = s.transition/10;
+
+ var selc=0; var ind=0;
+ populateSegments(s);
+ for (let i = 0; i < (s.seg||[]).length; i++)
+ {
+ if(s.seg[i].sel) {selc = ind; break;} ind++;
+ }
+ var i=s.seg[selc];
+ if (!i) {
+ showToast('No Segments!', true);
+ updateUI();
+ return;
+ }
+
+ selColors = i.col;
+ var cd = gId('csl').children;
+ for (let e = cd.length-1; e >= 0; e--)
+ {
+ var r,g,b,w;
+ if (Array.isArray(i.col[e])) {
+ r = i.col[e][0];
+ g = i.col[e][1];
+ b = i.col[e][2];
+ if (isRgbw) w = i.col[e][3];
+ } else {
+ // unsigned long RGBW (@blazoncek v2 experimental API implementation)
+ r = (i.col[e]>>16) & 0xFF;
+ g = (i.col[e]>> 8) & 0xFF;
+ b = (i.col[e] ) & 0xFF;
+ if (isRgbw) w = (i.col[e] >> 24) & 0xFF;
+ }
+ cd[e].style.backgroundColor = "rgb(" + r + "," + g + "," + b + ")";
+ if (isRgbw) whites[e] = parseInt(w);
+ selectSlot(csel);
+ }
+ gId('sliderW').value = whites[csel];
+
+ gId('sliderSpeed').value = i.sx;
+ gId('sliderIntensity').value = i.ix;
+
+ if (s.error && s.error != 0) {
+ var errstr = "";
+ switch (s.error) {
+ case 10:
+ errstr = "Could not mount filesystem!";
+ break;
+ case 11:
+ errstr = "Not enough space to save preset!";
+ break;
+ case 12:
+ errstr = "Preset not found.";
+ break;
+ case 13:
+ errstr = "Missing IR.json.";
+ break;
+ case 19:
+ errstr = "A filesystem error has occured.";
+ break;
+ }
+ showToast('Error ' + s.error + ": " + errstr, true);
+ }
+
+ selectedPal = i.pal;
+ selectedFx = i.fx;
+ updateUI(true);
+}
+
+var jsonTimeout;
+var reqsLegal = false;
+
+function requestJson(command=null)
+{
+ gId('connind').style.backgroundColor = "#a90";
+ if (command && !reqsLegal) return; //stop post requests from chrome onchange event on page restore
+ if (!jsonTimeout) jsonTimeout = setTimeout(showErrorToast, 3000);
+ if (!command) command = {'v':true};
+ var req = null;
+ var url = (loc?`http://${locip}`:'') + '/json/state';
+ var useWs = (ws && ws.readyState === WebSocket.OPEN);
+ var type = command ? 'post':'get';
+
+ command.v = true; // force complete /json/si API response
+ command.time = Math.floor(Date.now() / 1000);
+ command.transition = tr;
+ req = JSON.stringify(command);
+ if (req.length > 1000) useWs = false; //do not send very long requests over websocket
+
+ if (useWs) {
+ ws.send(req?req:'{"v":true}');
+ return;
+ }
+
+ fetch(url, {
+ method: type,
+ headers: {
+ "Content-type": "application/json; charset=UTF-8"
+ },
+ body: req
+ })
+ .then(res => {
+ if (!res.ok) showErrorToast();
+ return res.json();
+ })
+ .then(json => {
+ clearTimeout(jsonTimeout);
+ jsonTimeout = null;
+ lastUpdate = new Date();
+ clearErrorToast();
+ gId('connind').style.backgroundColor = "#070";
+ if (!json) { showToast('Empty response', true); return; }
+ if (json.success) return;
+ var s = json.state ? json.state : json;
+ readState(s);
+ reqsLegal = true;
+ })
+ .catch(function (error) {
+ showToast(error, true);
+ console.log(error);
+ });
+}
+
+function togglePower()
+{
+ isOn = !isOn;
+ var obj = {"on": isOn};
+ requestJson(obj);
+}
+
+function toggleInfo()
+{
+ if (isNodes) toggleNodes();
+ isInfo = !isInfo;
+ if (isInfo) loadInfo();
+ gId('info').style.transform = (isInfo) ? "translateY(0px)":"translateY(100%)";
+}
+
+function toggleNodes()
+{
+ if (isInfo) toggleInfo();
+ isNodes = !isNodes;
+ if (isNodes) loadNodes();
+ gId('nodes').style.transform = (isNodes) ? "translateY(0px)":"translateY(100%)";
+}
+
+function tglBri(b=null)
+{
+ if (b===null) b = gId(`briwrap`).style.display === "block";
+ gId('briwrap').style.display = !b ? "block":"none";
+ gId('buttonBri').className = !b ? "active":"";
+ size();
+}
+
+function tglCP()
+{
+ var p = gId(`picker`).style.display === "block";
+ gId('buttonCP').className = !p ? "active":"";
+ gId('picker').style.display = !p ? "block":"none";
+ var csl = gId(`csl`).style.display === "block";
+ gId('csl').style.display = !csl ? "block":"none";
+ var ps = gId(`Presets`).style.display === "block";
+ gId('Presets').style.display = !ps ? "block":"none";
+}
+
+function tglCs(i)
+{
+ var pss = gId(`p${i}cstgl`).checked;
+ gId(`p${i}o1`).style.display = pss? "block" : "none";
+ gId(`p${i}o2`).style.display = !pss? "block" : "none";
+}
+
+function tglPalDropdown()
+{
+ var p = gId('palDropdown').style;
+ p.display = (p.display==='block'?'none':'block');
+ gId('fxDropdown').style.display = 'none';
+ if (p.display==='block')
+ gId('palDropdown').scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ });
+}
+
+function tglFxDropdown()
+{
+ var p = gId('fxDropdown').style;
+ p.display = (p.display==='block'?'none':'block');
+ gId('palDropdown').style.display = 'none';
+ if (p.display==='block')
+ gId('fxDropdown').scrollIntoView({
+ behavior: 'smooth',
+ block: 'center',
+ });
+}
+
+function setSegPwr(s)
+{
+ var obj = {"seg": {"id": s, "on": !powered[s]}};
+ requestJson(obj);
+}
+
+function setSegBri(s)
+{
+ var obj = {"seg": {"id": s, "bri": parseInt(gId(`seg${s}bri`).value)}};
+ requestJson(obj);
+}
+
+function setEffect(ind = null)
+{
+ tglFxDropdown();
+ var obj = {"seg": {"fx": parseInt(ind)}};
+ requestJson(obj);
+}
+
+function setPalette(paletteId = null)
+{
+ tglPalDropdown();
+ var obj = {"seg": {"pal": paletteId}};
+ requestJson(obj);
+}
+
+function setBri()
+{
+ var obj = {"bri": parseInt(gId('sliderBri').value)};
+ requestJson(obj);
+}
+
+function setSpeed()
+{
+ var obj = {"seg": {"sx": parseInt(gId('sliderSpeed').value)}};
+ requestJson(obj);
+}
+
+function setIntensity()
+{
+ var obj = {"seg": {"ix": parseInt(gId('sliderIntensity').value)}};
+ requestJson(obj);
+}
+
+function setLor(i)
+{
+ var obj = {"lor": i};
+ requestJson(obj);
+}
+
+function setPreset(i)
+{
+ var obj = {"ps": i};
+ if (isPlaylist(i)) obj.on = true;
+ showToast("Loading preset " + pName(i) +" (" + i + ")");
+ requestJson(obj);
+}
+
+function selectSlot(b)
+{
+ csel = b;
+ var cd = gId('csl').children;
+ for (let i = 0; i < cd.length; i++) {
+ cd[i].style.border="2px solid var(--c-e)";
+ cd[i].style.margin="5px";
+ cd[i].style.width="46px";
+ }
+ cd[csel].style.border="5px solid var(--c-e)";
+ cd[csel].style.margin="2px";
+ cd[csel].style.width="54px";
+ cpick.color.set(cd[csel].style.backgroundColor);
+ gId('sliderW').value = whites[csel];
+ updateTrail(gId('sliderW'));
+ redrawPalPrev();
+}
+
+var lasth = 0;
+function pC(col)
+{
+ if (col == "rnd") {
+ col = {h: 0, s: 0, v: 100};
+ col.s = Math.floor((Math.random() * 50) + 50);
+ do {
+ col.h = Math.floor(Math.random() * 360);
+ } while (Math.abs(col.h - lasth) < 50);
+ lasth = col.h;
+ }
+ cpick.color.set(col);
+ setColor(0);
+}
+
+function setColor(sr)
+{
+ var cd = gId('csl').children;
+ if (sr == 1 && cd[csel].style.backgroundColor == 'rgb(0, 0, 0)') cpick.color.setChannel('hsv', 'v', 100);
+ cd[csel].style.backgroundColor = cpick.color.rgbString;
+ if (sr != 2) whites[csel] = gId('sliderW').value;
+ var col = cpick.color.rgb;
+ var obj = {"seg": {"col": [[col.r, col.g, col.b, whites[csel]],[],[]]}};
+ if (sr==1 || gId(`picker`).style.display !== "block") obj.seg.fx = 0;
+ if (csel == 1) {
+ obj = {"seg": {"col": [[],[col.r, col.g, col.b, whites[csel]],[]]}};
+ } else if (csel == 2) {
+ obj = {"seg": {"col": [[],[],[col.r, col.g, col.b, whites[csel]]]}};
+ }
+ requestJson(obj);
+}
+
+var hc = 0;
+setInterval(function(){if (!isInfo) return; hc+=18; if (hc>300) hc=0; if (hc>200)hc=306; if (hc==144) hc+=36; if (hc==108) hc+=18;
+gId('heart').style.color = `hsl(${hc}, 100%, 50%)`;}, 910);
+
+function openGH() { window.open("https://github.com/Aircoookie/WLED/wiki"); }
+
+var cnfr = false;
+function cnfReset()
+{
+ if (!cnfr) {
+ var bt = gId('resetbtn');
+ bt.style.color = "#f00";
+ bt.innerHTML = "Confirm Reboot";
+ cnfr = true; return;
+ }
+ window.location.href = "/reset";
+}
+
+function loadPalettesData(callback = null)
+{
+ if (palettesData) return;
+ const lsKey = "wledPalx";
+ var palettesDataJson = localStorage.getItem(lsKey);
+ if (palettesDataJson) {
+ try {
+ palettesDataJson = JSON.parse(palettesDataJson);
+ if (palettesDataJson && palettesDataJson.vid == lastinfo.vid) {
+ palettesData = palettesDataJson.p;
+ //redrawPalPrev() //TODO!
+ if (callback) callback();
+ return;
+ }
+ } catch (e) {}
+ }
+
+ palettesData = {};
+ getPalettesData(0, function() {
+ localStorage.setItem(lsKey, JSON.stringify({
+ p: palettesData,
+ vid: lastinfo.vid
+ }));
+ //redrawPalPrev();
+ if (callback) setTimeout(callback, 99); //go on to connect websocket
+ });
+}
+
+function getPalettesData(page, callback)
+{
+ var url = (loc?`http://${locip}`:'') + `/json/palx?page=${page}`;
+
+ fetch(url, {
+ method: 'get',
+ headers: {
+ "Content-type": "application/json; charset=UTF-8"
+ }
+ })
+ .then(res => {
+ if (!res.ok) showErrorToast();
+ return res.json();
+ })
+ .then(json => {
+ palettesData = Object.assign({}, palettesData, json.p);
+ if (page < json.m) setTimeout(function() { getPalettesData(page + 1, callback); }, 50);
+ else callback();
+ })
+ .catch(function(error) {
+ showToast(error, true);
+ console.log(error);
+ });
+}
+
+function search(f,l=null)
+{
+ f.nextElementSibling.style.display=(f.value!=='')?'block':'none';
+ if (!l) return;
+ var el = gId(l).querySelectorAll('.lstI');
+ for (i = 0; i < el.length; i++) {
+ var it = el[i];
+ var itT = it.querySelector('.lstIname').innerText.toUpperCase();
+ it.style.display = itT.indexOf(f.value.toUpperCase())>-1?'':'none';
+ }
+}
+
+function clean(c)
+{
+ c.style.display='none';
+ var i=c.previousElementSibling;
+ i.value='';
+ i.focus();
+ i.dispatchEvent(new Event('input'));
+}
+
+function unfocusSliders()
+{
+ gId("sliderBri").blur();
+ gId("sliderSpeed").blur();
+ gId("sliderIntensity").blur();
+}
+
+//sliding UI
+const _C = d.querySelector('.container'), N = 1;
+
+let iSlide = 0, x0 = null, scrollS = 0, locked = false, w;
+
+function unify(e) { return e.changedTouches ? e.changedTouches[0] : e; }
+
+function hasIroClass(classList)
+{
+ for (var i = 0; i < classList.length; i++) {
+ var element = classList[i];
+ if (element.startsWith('Iro')) return true;
+ }
+ return false;
+}
+
+function lock(e)
+{
+ var l = e.target.classList;
+ var pl = e.target.parentElement.classList;
+
+ if (l.contains('noslide') || hasIroClass(l) || hasIroClass(pl)) return;
+
+ x0 = unify(e).clientX;
+ scrollS = gEBCN("tabcontent")[iSlide].scrollTop;
+
+ _C.classList.toggle('smooth', !(locked = true));
+}
+
+function move(e)
+{
+ if(!locked) return;
+ var clientX = unify(e).clientX;
+ var dx = clientX - x0;
+ var s = Math.sign(dx);
+ var f = +(s*dx/w).toFixed(2);
+
+ if((clientX != 0) &&
+ (iSlide > 0 || s < 0) && (iSlide < N - 1 || s > 0) &&
+ f > 0.12 &&
+ gEBCN("tabcontent")[iSlide].scrollTop == scrollS)
+ {
+ _C.style.setProperty('--i', iSlide -= s);
+ f = 1 - f;
+ updateTablinks(iSlide);
+ }
+ _C.style.setProperty('--f', f);
+ _C.classList.toggle('smooth', !(locked = false));
+ x0 = null;
+}
+
+function size()
+{
+ w = window.innerWidth;
+ var h = gId('top').clientHeight;
+ sCol('--th', h + "px");
+ sCol("--tp", h - (gId(`briwrap`).style.display === "block" ? 0 : gId(`briwrap`).clientTop) + "px");
+ sCol("--bh", "0px");
+}
+
+function mergeDeep(target, ...sources)
+{
+ if (!sources.length) return target;
+ const source = sources.shift();
+
+ if (isO(target) && isO(source)) {
+ for (const key in source) {
+ if (isO(source[key])) {
+ if (!target[key]) Object.assign(target, { [key]: {} });
+ mergeDeep(target[key], source[key]);
+ } else {
+ Object.assign(target, { [key]: source[key] });
+ }
+ }
+ }
+ return mergeDeep(target, ...sources);
+}
+
+size();
+window.addEventListener('resize', size, false);
+
+_C.addEventListener('mousedown', lock, false);
+_C.addEventListener('touchstart', lock, false);
+
+_C.addEventListener('mouseout', move, false);
+_C.addEventListener('mouseup', move, false);
+_C.addEventListener('touchend', move, false);
diff --git a/wled00/file.cpp b/wled00/file.cpp
index 0d0f59ca5..ed032c66f 100644
--- a/wled00/file.cpp
+++ b/wled00/file.cpp
@@ -380,10 +380,10 @@ String getContentType(AsyncWebServerRequest* request, String filename){
else if(filename.endsWith(".htm")) return "text/html";
else if(filename.endsWith(".html")) return "text/html";
else if(filename.endsWith(".css")) return "text/css";
-// else if(filename.endsWith(".js")) return "application/javascript";
+ else if(filename.endsWith(".js")) return "application/javascript";
else if(filename.endsWith(".json")) return "application/json";
else if(filename.endsWith(".png")) return "image/png";
-// else if(filename.endsWith(".gif")) return "image/gif";
+ else if(filename.endsWith(".gif")) return "image/gif";
else if(filename.endsWith(".jpg")) return "image/jpeg";
else if(filename.endsWith(".ico")) return "image/x-icon";
// else if(filename.endsWith(".xml")) return "text/xml";
diff --git a/wled00/html_other.h b/wled00/html_other.h
index 3d84852f6..f311faf5d 100644
--- a/wled00/html_other.h
+++ b/wled00/html_other.h
@@ -42,7 +42,7 @@ function B(){window.history.back()}function U(){document.getElementById("uf").st
.bt{background:#333;color:#fff;font-family:Verdana,sans-serif;border:.3ch solid #333;display:inline-block;font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:Verdana,sans-serif;text-align:center;background:#222;color:#fff;line-height:200%}#msg{display:none}
WLED Software Update