From dcfbf2b1544e2eba6ce6186d50e64264500caec7 Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Tue, 10 Aug 2021 17:11:17 +0200 Subject: [PATCH] Simplified UI and general UI polishing (CSS, HTML & JS). Boot transition fix. Local storage invalidation when uploading presets.json. --- package.json | 2 +- platformio.ini | 6 +- tools/cdata.js | 9 +- wled00/cfg.cpp | 4 + wled00/data/index.css | 277 +-- wled00/data/index.htm | 10 +- wled00/data/index.js | 106 +- wled00/data/settings_ui.htm | 8 +- wled00/data/simple.css | 758 ++++++ wled00/data/simple.htm | 172 ++ wled00/data/simple.js | 1383 +++++++++++ wled00/file.cpp | 4 +- wled00/html_other.h | 2 +- wled00/html_settings.h | 7 +- wled00/html_simple.h | 1791 ++++++++++++++ wled00/html_ui.h | 4459 ++++++++++++++++++----------------- wled00/json.cpp | 1 + wled00/led.cpp | 8 +- wled00/set.cpp | 3 + wled00/wled.h | 6 +- wled00/wled_server.cpp | 15 +- wled00/xml.cpp | 1 + 22 files changed, 6565 insertions(+), 2467 deletions(-) create mode 100644 wled00/data/simple.css create mode 100644 wled00/data/simple.htm create mode 100644 wled00/data/simple.js create mode 100644 wled00/html_simple.h diff --git a/package.json b/package.json index c5dd0113..302312ce 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 611a6614..87d85fd0 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 8a904843..fee10a54 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 930a4206..a83a1956 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 3d6bd359..da0a4cc4 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 7c592e68..49bbac5a 100644 --- a/wled00/data/index.htm +++ b/wled00/data/index.htm @@ -24,7 +24,7 @@ - + @@ -164,18 +164,18 @@
-
-
Loading...
+
+

Transition: s

-
+
@@ -190,7 +190,7 @@ - +
diff --git a/wled00/data/index.js b/wled00/data/index.js index 4d8f1868..67b1e544 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 += ``; - it++; - if (it > 4) { - it = 0; - cn += '
'; - } + cn += ``; } - 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 = `
-
+ @@ -1347,7 +1345,7 @@ function makeSeg() - +
Start LED
${ledCount - ns} LEDs
@@ -1359,7 +1357,7 @@ function makeSeg() function resetUtil() { - gId('segutil').innerHTML = '
'; + gId('segutil').innerHTML = ''; 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()}>
- ${(i>0)?' + ${(i>0)?'
${(i>0)? ('
ID ' +i+ '
'):""}`; @@ -1510,21 +1510,26 @@ ${(i>0)? ('
ID ' +i+ '
'):""}`; function makePUtil() { - gId('putil').innerHTML = `
${makeP(0)}
`; + gId('putil').classList.remove("staytop"); + gId('putil').scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + gId('putil').innerHTML = `
${makeP(0)}
`; for (var i=0; i
- +
- + @@ -1532,9 +1537,9 @@ function makePlEntry(p,i) { - - - + + +
Duration#${i+1}
ssss
`; @@ -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 = `
${makeP(0,true)}
`; + gId('putil').classList.remove("staytop"); + gId('putil').scrollIntoView({ + behavior: 'smooth', + block: 'start', + }); + gId('putil').innerHTML = `
${makeP(0,true)}
`; refreshPlE(0); } function resetPUtil() { - var cn = ``+ - `
`; + gId('putil').classList.add("staytop"); + var cn = ``+ + ``; 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 cf63fb79..9c72c3ac 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 00000000..08a627b3 --- /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 00000000..b2e3be40 --- /dev/null +++ b/wled00/data/simple.htm @@ -0,0 +1,172 @@ + + + + + + + + + WLED + + + + + +
Loading WLED UI...
+ +
+ +
+
+
+ + +
+ + + +
+
+

Global brightness

+
+ +
+ +
+
+ +
+
+
+
+ +
+
+
+ +
+

White channel

+ +
+ +
+
+ +
+ +
+
+
+
+
+
+

+
+
+
+
+
R
+
+ + +
+
+
+ +
+

Quick Load

+
+
+ + + +
+

Effect

+ +
+ +
+ +
+
+ +
+ +
+ +
+ +
+
+ +
+
+
Solid
+
Default
+
+
+ + +
+
+
+ +
+
+
+
+ + +
+
+
+ +
+
+
+
+
+
+
+ +
+
+
+ + + + + +
+ + + + + diff --git a/wled00/data/simple.js b/wled00/data/simple.js new file mode 100644 index 00000000..7cc25a28 --- /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 += ``; + } + } + 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 ``; +} + +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 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 0d0f59ca..ed032c66 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 3d84852f..f311faf5 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

-Installed version: 0.13.0-bl2
Download the latest binary: Download the latest binary:

UI Settings