Skip to content

Commit 2e9bd47

Browse files
Upload files & skinning (wled#2084)
* Skinning WLED & uploading files. Backup & restore configuration & presets. External holidays.json * Option for segment count instead of stop. * Small fixes and improvements * Further improvements * Enable custom CSS by default Co-authored-by: Christian Schwinne <[email protected]>
1 parent b058fb8 commit 2e9bd47

File tree

10 files changed

+1107
-881
lines changed

10 files changed

+1107
-881
lines changed

wled00/data/index.js

Lines changed: 54 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,16 @@ var ws;
2626
var fxlist = d.getElementById('fxlist'), pallist = d.getElementById('pallist');
2727
var cfg = {
2828
theme:{base:"dark", bg:{url:""}, alpha:{bg:0.6,tab:0.8}, color:{bg:""}},
29-
comp :{colors:{picker: true, rgb: false, quick: true, hex: false}, labels:true, pcmbot:false, pid:true}
29+
comp :{colors:{picker: true, rgb: false, quick: true, hex: false},
30+
labels:true, pcmbot:false, pid:true, seglen:false, css:true, hdays:false}
3031
};
32+
var hol = [
33+
[0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas
34+
[0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day
35+
[2022,3,17,2,"https://aircoookie.github.io/easter.png"],
36+
[2023,3,9,2,"https://aircoookie.github.io/easter.png"],
37+
[2024,2,31,2,"https://aircoookie.github.io/easter.png"]
38+
];
3139

3240
var cpick = new iro.ColorPicker("#picker", {
3341
width: 260,
@@ -158,13 +166,6 @@ function loadBg(iUrl) {
158166
img.src = iUrl;
159167
if (iUrl == "") {
160168
var today = new Date();
161-
var hol = [
162-
[0,11,24,4,"https://aircoookie.github.io/xmas.png"], // christmas
163-
[0,2,17,1,"https://images.alphacoders.com/491/491123.jpg"], // st. Patrick's day
164-
[2022,3,17,2,"https://aircoookie.github.io/easter.png"],
165-
[2023,3,9,2,"https://aircoookie.github.io/easter.png"],
166-
[2024,2,31,2,"https://aircoookie.github.io/easter.png"]
167-
];
168169
for (var i=0; i<hol.length; i++) {
169170
var yr = hol[i][0]==0 ? today.getFullYear() : hol[i][0];
170171
var hs = new Date(yr,hol[i][1],hol[i][2]);
@@ -182,6 +183,21 @@ function loadBg(iUrl) {
182183
});
183184
}
184185

186+
function loadSkinCSS(cId)
187+
{
188+
if (!d.getElementById(cId)) // check if element exists
189+
{
190+
var h = document.getElementsByTagName('head')[0];
191+
var l = document.createElement('link');
192+
l.id = cId;
193+
l.rel = 'stylesheet';
194+
l.type = 'text/css';
195+
l.href = (loc?`http://${locip}`:'.') + '/skin.css';
196+
l.media = 'all';
197+
h.appendChild(l);
198+
}
199+
}
200+
185201
function onLoad() {
186202
if (window.location.protocol == "file:") {
187203
loc = true;
@@ -198,7 +214,27 @@ function onLoad() {
198214
resetPUtil();
199215

200216
applyCfg();
201-
loadBg(cfg.theme.bg.url);
217+
if (cfg.comp.hdays) { //load custom holiday list
218+
fetch((loc?`http://${locip}`:'.') + "/holidays.json", { // may be loaded from external source
219+
method: 'get'
220+
})
221+
.then(res => {
222+
//if (!res.ok) showErrorToast();
223+
return res.json();
224+
})
225+
.then(json => {
226+
if (Array.isArray(json)) hol = json;
227+
//TODO: do some parsing first
228+
})
229+
.catch(function (error) {
230+
console.log("holidays.json does not contain array of holidays. Defaults loaded.");
231+
})
232+
.finally(function(){
233+
loadBg(cfg.theme.bg.url);
234+
});
235+
} else
236+
loadBg(cfg.theme.bg.url);
237+
if (cfg.comp.css) loadSkinCSS('skinCss');
202238

203239
var cd = d.getElementById('csl').children;
204240
for (var i = 0; i < cd.length; i++) {
@@ -211,7 +247,7 @@ function onLoad() {
211247
setColor(1);
212248
});
213249
pmtLS = localStorage.getItem('wledPmt');
214-
setTimeout(function(){requestJson(null, false);}, 25);
250+
setTimeout(function(){requestJson(null, false);}, 50);
215251
d.addEventListener("visibilitychange", handleVisibilityChange, false);
216252
size();
217253
d.getElementById("cv").style.opacity=0;
@@ -557,12 +593,12 @@ function populateSegments(s)
557593
<table class="infot">
558594
<tr>
559595
<td class="segtd">Start LED</td>
560-
<td class="segtd">Stop LED</td>
596+
<td class="segtd">${cfg.comp.seglen?"Length":"Stop LED"}</td>
561597
<td class="segtd">Offset</td>
562598
</tr>
563599
<tr>
564600
<td class="segtd"><input class="noslide segn" id="seg${i}s" type="number" min="0" max="${ledCount-1}" value="${inst.start}" oninput="updateLen(${i})"></td>
565-
<td class="segtd"><input class="noslide segn" id="seg${i}e" type="number" min="0" max="${ledCount}" value="${inst.stop}" oninput="updateLen(${i})"></td>
601+
<td class="segtd"><input class="noslide segn" id="seg${i}e" type="number" min="0" max="${ledCount-(cfg.comp.seglen?inst.start:0)}" value="${inst.stop-(cfg.comp.seglen?inst.start:0)}" oninput="updateLen(${i})"></td>
566602
<td class="segtd"><input class="noslide segn" id="seg${i}of" type="number" value="${inst.of}" oninput="updateLen(${i})"></td>
567603
</tr>
568604
</table>
@@ -661,13 +697,12 @@ function populatePalettes(palettes)
661697
var html = `<div class="searchbar"><input type="text" class="search" placeholder="Search" oninput="search(this)" />
662698
<i class="icons search-cancel-icon" onclick="cancelSearch(this)">&#xe38f;</i></div>`;
663699
for (let i = 0; i < palettes.length; i++) {
664-
let previewCss = genPalPrevCss(palettes[i].id);
665700
html += generateListItemHtml(
666701
'palette',
667702
palettes[i].id,
668703
palettes[i].name,
669704
'setPalette',
670-
`<div class="lstIprev" style="${previewCss}"></div>`,
705+
`<div class="lstIprev" style="${genPalPrevCss(palettes[i].id)}"></div>`,
671706
palettes[i].class,
672707
);
673708
}
@@ -693,7 +728,6 @@ function genPalPrevCss(id)
693728
return;
694729
}
695730
var paletteData = palettesData[id];
696-
var previewCss = "";
697731

698732
if (!paletteData) {
699733
return 'display: none';
@@ -855,7 +889,7 @@ function updateLen(s)
855889
if (!d.getElementById(`seg${s}s`)) return;
856890
var start = parseInt(d.getElementById(`seg${s}s`).value);
857891
var stop = parseInt(d.getElementById(`seg${s}e`).value);
858-
var len = stop - start;
892+
var len = stop - (cfg.comp.seglen?0:start);
859893
var out = "(delete)";
860894
if (len > 1) {
861895
out = `${len} LEDs`;
@@ -1218,7 +1252,7 @@ function toggleNodes() {
12181252
function makeSeg() {
12191253
var ns = 0;
12201254
if (lowestUnused > 0) {
1221-
var pend = d.getElementById(`seg${lowestUnused -1}e`).value;
1255+
var pend = parseInt(d.getElementById(`seg${lowestUnused -1}e`).value,10) + (cfg.comp.seglen?parseInt(d.getElementById(`seg${lowestUnused -1}s`).value,10):0);
12221256
if (pend < ledCount) ns = pend;
12231257
}
12241258
var cn = `<div class="seg">
@@ -1230,11 +1264,11 @@ function makeSeg() {
12301264
<table class="segt">
12311265
<tr>
12321266
<td class="segtd">Start LED</td>
1233-
<td class="segtd">Stop LED</td>
1267+
<td class="segtd">${cfg.comp.seglen?"Length":"Stop LED"}</td>
12341268
</tr>
12351269
<tr>
12361270
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}s" type="number" min="0" max="${ledCount-1}" value="${ns}" oninput="updateLen(${lowestUnused})"></td>
1237-
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}e" type="number" min="0" max="${ledCount}" value="${ledCount}" oninput="updateLen(${lowestUnused})"></td>
1271+
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}e" type="number" min="0" max="${ledCount-(cfg.comp.seglen?ns:0)}" value="${ledCount-(cfg.comp.seglen?ns:0)}" oninput="updateLen(${lowestUnused})"></td>
12381272
</tr>
12391273
</table>
12401274
<div class="h" id="seg${lowestUnused}len">${ledCount - ns} LED${ledCount - ns >1 ? "s":""}</div>
@@ -1467,7 +1501,7 @@ function setSeg(s){
14671501
var start = parseInt(d.getElementById(`seg${s}s`).value);
14681502
var stop = parseInt(d.getElementById(`seg${s}e`).value);
14691503
if (stop <= start) {delSeg(s); return;}
1470-
var obj = {"seg": {"id": s, "start": start, "stop": stop}};
1504+
var obj = {"seg": {"id": s, "start": start, "stop": (cfg.comp.seglen?start:0)+stop}};
14711505
if (d.getElementById(`seg${s}grp`))
14721506
{
14731507
var grp = parseInt(d.getElementById(`seg${s}grp`).value);

wled00/data/settings_leds.htm

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@
1818
function off(n){
1919
d.getElementsByName(n)[0].value = -1;
2020
}
21+
var timeout;
22+
function showToast(text, error = false)
23+
{
24+
var x = gId("toast");
25+
x.innerHTML = text;
26+
x.className = error ? "error":"show";
27+
clearTimeout(timeout);
28+
x.style.animation = 'none';
29+
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
30+
}
2131
function bLimits(b,p,m) {
2232
maxB = b; maxM = m; maxPB = p;
2333
}
@@ -204,6 +214,7 @@
204214
s2 += "A is enough)<br>";
205215
gId('psu').innerHTML = s;
206216
gId('psu2').innerHTML = isWS2815 ? "" : s2;
217+
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
207218
}
208219
function lastEnd(i) {
209220
if (i<1) return 0;
@@ -293,6 +304,17 @@
293304
c += `</select>`;
294305
c += `<span style="cursor: pointer;" onclick="off('${bt}')">&nbsp;&#215;</span><br>`;
295306
gId("btns").innerHTML = c;
307+
}
308+
function uploadFile(name) {
309+
var req = new XMLHttpRequest();
310+
req.addEventListener('load', function(){showToast(this.responseText)});
311+
req.addEventListener('error', function(e){showToast(e.stack,true);});
312+
req.open("POST", "/upload");
313+
var formData = new FormData();
314+
formData.append("data", d.Sf.data.files[0], name);
315+
req.send(formData);
316+
d.Sf.data.value = '';
317+
return false;
296318
}
297319
function GetV()
298320
{
@@ -350,7 +372,7 @@ <h3>Hardware setup</h3>
350372
</div><hr style="width:260px">
351373
<div id="btns"></div>
352374
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
353-
IR pin: <input type="number" class="xs" min="-1" max="40" name="IR" onchange="UI()">&nbsp;<select name="IT">
375+
IR pin: <input type="number" class="xs" min="-1" max="40" name="IR" onchange="UI()">&nbsp;<select name="IT" onchange="UI()">
354376
<option value=0>Remote disabled</option>
355377
<option value=1>24-key RGB</option>
356378
<option value=2>24-key with CT</option>
@@ -361,6 +383,8 @@ <h3>Hardware setup</h3>
361383
<option value=7>9-key red</option>
362384
<option value=8>JSON remote</option>
363385
</select><span style="cursor: pointer;" onclick="off('IR')">&nbsp;&#215;</span><br>
386+
<div id="json" style="display:none;">JSON file: <input type="file" name="data" accept=".json"> <input type="button" value="Upload" onclick="uploadFile('/ir.json');"><br></div>
387+
<div id="toast"></div>
364388
<a href="https://github.com/Aircoookie/WLED/wiki/Infrared-Control" target="_blank">IR info</a><br>
365389
Relay pin: <input type="number" class="xs" min="-1" max="33" name="RL" onchange="UI()"> Invert <input type="checkbox" name="RM"><span style="cursor: pointer;" onclick="off('RL')">&nbsp;&#215;</span><br>
366390
<hr style="width:260px">
@@ -404,7 +428,7 @@ <h3>Advanced</h3>
404428
<option value=4>Legacy</option>
405429
</select>
406430
<br></span><hr>
407-
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
431+
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
408432
</form>
409433
</body>
410434
</html>

wled00/data/settings_sec.htm

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<meta charset="utf-8">
66
<title>Misc Settings</title>
77
<script>
8+
var d = document;
89
function H()
910
{
1011
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#security-settings");
@@ -17,6 +18,34 @@
1718
{
1819
window.open("/update","_self");
1920
}
21+
function gId(s)
22+
{
23+
return d.getElementById(s);
24+
}
25+
function isObject(item) {
26+
return (item && typeof item === 'object' && !Array.isArray(item));
27+
}
28+
var timeout;
29+
function showToast(text, error = false)
30+
{
31+
var x = gId("toast");
32+
x.innerHTML = text;
33+
x.className = error ? "error":"show";
34+
clearTimeout(timeout);
35+
x.style.animation = 'none';
36+
timeout = setTimeout(function(){ x.className = x.className.replace("show", ""); }, 2900);
37+
}
38+
function uploadFile(fO,name) {
39+
var req = new XMLHttpRequest();
40+
req.addEventListener('load', function(){showToast(this.responseText)});
41+
req.addEventListener('error', function(e){showToast(e.stack,true);});
42+
req.open("POST", "/upload");
43+
var formData = new FormData();
44+
formData.append("data", fO.files[0], name);
45+
req.send(formData);
46+
fO.value = '';
47+
return false;
48+
}
2049
function GetV()
2150
{
2251
//values injected by server while sending HTML
@@ -44,14 +73,24 @@ <h2>Security & Update setup</h2>
4473
<h3>Software Update</h3>
4574
<button type="button" onclick="U()">Manual OTA Update</button><br>
4675
Enable ArduinoOTA: <input type="checkbox" name="AO"><br>
76+
<h3>Backup & Restore</h3>
77+
<a class="btn lnk" href="/presets.json?download" target="download-frame">Backup presets</a><br>
78+
<div>Restore presets<br><input type="file" name="data" accept=".json"> <input type="button" value="Upload" onclick="uploadFile(d.Sf.data,'/presets.json');"><br></div><br>
79+
<a class="btn lnk" href="/cfg.json?download" target="download-frame">Backup configuration</a><br>
80+
<div>Restore configuration<br><input type="file" name="data2" accept=".json"> <input type="button" value="Upload" onclick="uploadFile(d.Sf.data2,'/cfg.json');"><br></div>
81+
<div style="color: #fa0;">&#9888; Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
82+
Incorrect configuration may require a factory reset or re-flashing of your ESP.</div>
83+
For security reasons, passwords are not backed up.
4784
<h3>About</h3>
4885
<a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a> version ##VERSION##<!-- Autoreplaced from package.json --><br><br>
4986
<a href="https://github.com/Aircoookie/WLED/wiki/Contributors-and-credits" target="_blank">Contributors, dependencies and special thanks</a><br>
5087
A huge thank you to everyone who helped me create WLED!<br><br>
5188
(c) 2016-2021 Christian Schwinne <br>
5289
<i>Licensed under the <a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
5390
Server message: <span class="sip"> Response error! </span><hr>
91+
<div id="toast"></div>
5492
<button type="button" onclick="B()">Back</button><button type="submit">Save & Reboot</button>
5593
</form>
94+
<iframe name=download-frame style='display:none;'></iframe>
5695
</body>
5796
</html>

0 commit comments

Comments
 (0)