Upload files & skinning (#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 <dev.aircoookie@gmail.com>
This commit is contained in:
parent
b058fb8db4
commit
2e9bd477d9
@ -26,8 +26,16 @@ var ws;
|
||||
var fxlist = d.getElementById('fxlist'), pallist = d.getElementById('pallist');
|
||||
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}
|
||||
comp :{colors:{picker: true, rgb: false, quick: true, hex: false},
|
||||
labels:true, pcmbot:false, pid:true, seglen:false, css:true, hdays: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,
|
||||
@ -158,13 +166,6 @@ function loadBg(iUrl) {
|
||||
img.src = iUrl;
|
||||
if (iUrl == "") {
|
||||
var today = new Date();
|
||||
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"]
|
||||
];
|
||||
for (var i=0; i<hol.length; i++) {
|
||||
var yr = hol[i][0]==0 ? today.getFullYear() : hol[i][0];
|
||||
var hs = new Date(yr,hol[i][1],hol[i][2]);
|
||||
@ -182,6 +183,21 @@ function loadBg(iUrl) {
|
||||
});
|
||||
}
|
||||
|
||||
function loadSkinCSS(cId)
|
||||
{
|
||||
if (!d.getElementById(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);
|
||||
}
|
||||
}
|
||||
|
||||
function onLoad() {
|
||||
if (window.location.protocol == "file:") {
|
||||
loc = true;
|
||||
@ -198,7 +214,27 @@ function onLoad() {
|
||||
resetPUtil();
|
||||
|
||||
applyCfg();
|
||||
loadBg(cfg.theme.bg.url);
|
||||
if (cfg.comp.hdays) { //load custom holiday list
|
||||
fetch((loc?`http://${locip}`:'.') + "/holidays.json", { // may be loaded from external source
|
||||
method: 'get'
|
||||
})
|
||||
.then(res => {
|
||||
//if (!res.ok) showErrorToast();
|
||||
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(function(){
|
||||
loadBg(cfg.theme.bg.url);
|
||||
});
|
||||
} else
|
||||
loadBg(cfg.theme.bg.url);
|
||||
if (cfg.comp.css) loadSkinCSS('skinCss');
|
||||
|
||||
var cd = d.getElementById('csl').children;
|
||||
for (var i = 0; i < cd.length; i++) {
|
||||
@ -211,7 +247,7 @@ function onLoad() {
|
||||
setColor(1);
|
||||
});
|
||||
pmtLS = localStorage.getItem('wledPmt');
|
||||
setTimeout(function(){requestJson(null, false);}, 25);
|
||||
setTimeout(function(){requestJson(null, false);}, 50);
|
||||
d.addEventListener("visibilitychange", handleVisibilityChange, false);
|
||||
size();
|
||||
d.getElementById("cv").style.opacity=0;
|
||||
@ -557,12 +593,12 @@ function populateSegments(s)
|
||||
<table class="infot">
|
||||
<tr>
|
||||
<td class="segtd">Start LED</td>
|
||||
<td class="segtd">Stop LED</td>
|
||||
<td class="segtd">${cfg.comp.seglen?"Length":"Stop LED"}</td>
|
||||
<td class="segtd">Offset</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<td class="segtd"><input class="noslide segn" id="seg${i}e" type="number" min="0" max="${ledCount}" value="${inst.stop}" oninput="updateLen(${i})"></td>
|
||||
<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>
|
||||
<td class="segtd"><input class="noslide segn" id="seg${i}of" type="number" value="${inst.of}" oninput="updateLen(${i})"></td>
|
||||
</tr>
|
||||
</table>
|
||||
@ -661,13 +697,12 @@ function populatePalettes(palettes)
|
||||
var html = `<div class="searchbar"><input type="text" class="search" placeholder="Search" oninput="search(this)" />
|
||||
<i class="icons search-cancel-icon" onclick="cancelSearch(this)"></i></div>`;
|
||||
for (let i = 0; i < palettes.length; i++) {
|
||||
let previewCss = genPalPrevCss(palettes[i].id);
|
||||
html += generateListItemHtml(
|
||||
'palette',
|
||||
palettes[i].id,
|
||||
palettes[i].name,
|
||||
'setPalette',
|
||||
`<div class="lstIprev" style="${previewCss}"></div>`,
|
||||
`<div class="lstIprev" style="${genPalPrevCss(palettes[i].id)}"></div>`,
|
||||
palettes[i].class,
|
||||
);
|
||||
}
|
||||
@ -693,7 +728,6 @@ function genPalPrevCss(id)
|
||||
return;
|
||||
}
|
||||
var paletteData = palettesData[id];
|
||||
var previewCss = "";
|
||||
|
||||
if (!paletteData) {
|
||||
return 'display: none';
|
||||
@ -855,7 +889,7 @@ function updateLen(s)
|
||||
if (!d.getElementById(`seg${s}s`)) return;
|
||||
var start = parseInt(d.getElementById(`seg${s}s`).value);
|
||||
var stop = parseInt(d.getElementById(`seg${s}e`).value);
|
||||
var len = stop - start;
|
||||
var len = stop - (cfg.comp.seglen?0:start);
|
||||
var out = "(delete)";
|
||||
if (len > 1) {
|
||||
out = `${len} LEDs`;
|
||||
@ -1218,7 +1252,7 @@ function toggleNodes() {
|
||||
function makeSeg() {
|
||||
var ns = 0;
|
||||
if (lowestUnused > 0) {
|
||||
var pend = d.getElementById(`seg${lowestUnused -1}e`).value;
|
||||
var pend = parseInt(d.getElementById(`seg${lowestUnused -1}e`).value,10) + (cfg.comp.seglen?parseInt(d.getElementById(`seg${lowestUnused -1}s`).value,10):0);
|
||||
if (pend < ledCount) ns = pend;
|
||||
}
|
||||
var cn = `<div class="seg">
|
||||
@ -1230,11 +1264,11 @@ function makeSeg() {
|
||||
<table class="segt">
|
||||
<tr>
|
||||
<td class="segtd">Start LED</td>
|
||||
<td class="segtd">Stop LED</td>
|
||||
<td class="segtd">${cfg.comp.seglen?"Length":"Stop LED"}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}s" type="number" min="0" max="${ledCount-1}" value="${ns}" oninput="updateLen(${lowestUnused})"></td>
|
||||
<td class="segtd"><input class="noslide segn" id="seg${lowestUnused}e" type="number" min="0" max="${ledCount}" value="${ledCount}" oninput="updateLen(${lowestUnused})"></td>
|
||||
<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>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="h" id="seg${lowestUnused}len">${ledCount - ns} LED${ledCount - ns >1 ? "s":""}</div>
|
||||
@ -1467,7 +1501,7 @@ function setSeg(s){
|
||||
var start = parseInt(d.getElementById(`seg${s}s`).value);
|
||||
var stop = parseInt(d.getElementById(`seg${s}e`).value);
|
||||
if (stop <= start) {delSeg(s); return;}
|
||||
var obj = {"seg": {"id": s, "start": start, "stop": stop}};
|
||||
var obj = {"seg": {"id": s, "start": start, "stop": (cfg.comp.seglen?start:0)+stop}};
|
||||
if (d.getElementById(`seg${s}grp`))
|
||||
{
|
||||
var grp = parseInt(d.getElementById(`seg${s}grp`).value);
|
||||
|
@ -18,6 +18,16 @@
|
||||
function off(n){
|
||||
d.getElementsByName(n)[0].value = -1;
|
||||
}
|
||||
var timeout;
|
||||
function showToast(text, error = false)
|
||||
{
|
||||
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 bLimits(b,p,m) {
|
||||
maxB = b; maxM = m; maxPB = p;
|
||||
}
|
||||
@ -204,6 +214,7 @@
|
||||
s2 += "A is enough)<br>";
|
||||
gId('psu').innerHTML = s;
|
||||
gId('psu2').innerHTML = isWS2815 ? "" : s2;
|
||||
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
|
||||
}
|
||||
function lastEnd(i) {
|
||||
if (i<1) return 0;
|
||||
@ -293,6 +304,17 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
|
||||
c += `</select>`;
|
||||
c += `<span style="cursor: pointer;" onclick="off('${bt}')"> ×</span><br>`;
|
||||
gId("btns").innerHTML = c;
|
||||
}
|
||||
function uploadFile(name) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.addEventListener('load', function(){showToast(this.responseText)});
|
||||
req.addEventListener('error', function(e){showToast(e.stack,true);});
|
||||
req.open("POST", "/upload");
|
||||
var formData = new FormData();
|
||||
formData.append("data", d.Sf.data.files[0], name);
|
||||
req.send(formData);
|
||||
d.Sf.data.value = '';
|
||||
return false;
|
||||
}
|
||||
function GetV()
|
||||
{
|
||||
@ -350,7 +372,7 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
|
||||
</div><hr style="width:260px">
|
||||
<div id="btns"></div>
|
||||
Touch threshold: <input type="number" class="s" min="0" max="100" name="TT" required><br>
|
||||
IR pin: <input type="number" class="xs" min="-1" max="40" name="IR" onchange="UI()"> <select name="IT">
|
||||
IR pin: <input type="number" class="xs" min="-1" max="40" name="IR" onchange="UI()"> <select name="IT" onchange="UI()">
|
||||
<option value=0>Remote disabled</option>
|
||||
<option value=1>24-key RGB</option>
|
||||
<option value=2>24-key with CT</option>
|
||||
@ -361,6 +383,8 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
|
||||
<option value=7>9-key red</option>
|
||||
<option value=8>JSON remote</option>
|
||||
</select><span style="cursor: pointer;" onclick="off('IR')"> ×</span><br>
|
||||
<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>
|
||||
<div id="toast"></div>
|
||||
<a href="https://github.com/Aircoookie/WLED/wiki/Infrared-Control" target="_blank">IR info</a><br>
|
||||
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')"> ×</span><br>
|
||||
<hr style="width:260px">
|
||||
@ -404,7 +428,7 @@ Reverse (rotated 180°): <input type="checkbox" name="CV${i}">
|
||||
<option value=4>Legacy</option>
|
||||
</select>
|
||||
<br></span><hr>
|
||||
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
||||
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,6 +5,7 @@
|
||||
<meta charset="utf-8">
|
||||
<title>Misc Settings</title>
|
||||
<script>
|
||||
var d = document;
|
||||
function H()
|
||||
{
|
||||
window.open("https://github.com/Aircoookie/WLED/wiki/Settings#security-settings");
|
||||
@ -17,6 +18,34 @@
|
||||
{
|
||||
window.open("/update","_self");
|
||||
}
|
||||
function gId(s)
|
||||
{
|
||||
return d.getElementById(s);
|
||||
}
|
||||
function isObject(item) {
|
||||
return (item && typeof item === 'object' && !Array.isArray(item));
|
||||
}
|
||||
var timeout;
|
||||
function showToast(text, error = false)
|
||||
{
|
||||
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 uploadFile(fO,name) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.addEventListener('load', function(){showToast(this.responseText)});
|
||||
req.addEventListener('error', function(e){showToast(e.stack,true);});
|
||||
req.open("POST", "/upload");
|
||||
var formData = new FormData();
|
||||
formData.append("data", fO.files[0], name);
|
||||
req.send(formData);
|
||||
fO.value = '';
|
||||
return false;
|
||||
}
|
||||
function GetV()
|
||||
{
|
||||
//values injected by server while sending HTML
|
||||
@ -44,6 +73,14 @@
|
||||
<h3>Software Update</h3>
|
||||
<button type="button" onclick="U()">Manual OTA Update</button><br>
|
||||
Enable ArduinoOTA: <input type="checkbox" name="AO"><br>
|
||||
<h3>Backup & Restore</h3>
|
||||
<a class="btn lnk" href="/presets.json?download" target="download-frame">Backup presets</a><br>
|
||||
<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>
|
||||
<a class="btn lnk" href="/cfg.json?download" target="download-frame">Backup configuration</a><br>
|
||||
<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>
|
||||
<div style="color: #fa0;">⚠ Restoring presets/configuration will OVERWRITE your current presets/configuration.<br>
|
||||
Incorrect configuration may require a factory reset or re-flashing of your ESP.</div>
|
||||
For security reasons, passwords are not backed up.
|
||||
<h3>About</h3>
|
||||
<a href="https://github.com/Aircoookie/WLED/" target="_blank">WLED</a> version ##VERSION##<!-- Autoreplaced from package.json --><br><br>
|
||||
<a href="https://github.com/Aircoookie/WLED/wiki/Contributors-and-credits" target="_blank">Contributors, dependencies and special thanks</a><br>
|
||||
@ -51,7 +88,9 @@
|
||||
(c) 2016-2021 Christian Schwinne <br>
|
||||
<i>Licensed under the <a href="https://github.com/Aircoookie/WLED/blob/master/LICENSE" target="_blank">MIT license</a></i><br><br>
|
||||
Server message: <span class="sip"> Response error! </span><hr>
|
||||
<div id="toast"></div>
|
||||
<button type="button" onclick="B()">Back</button><button type="submit">Save & Reboot</button>
|
||||
</form>
|
||||
<iframe name=download-frame style='display:none;'></iframe>
|
||||
</body>
|
||||
</html>
|
@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head lang="en">
|
||||
<meta charset="utf-8">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=500">
|
||||
<title>UI Settings</title>
|
||||
<script>
|
||||
@ -10,16 +10,19 @@
|
||||
var sett = null;
|
||||
var l = {
|
||||
"comp":{
|
||||
"labels":"Show button labels",
|
||||
"colors":{
|
||||
"LABEL":"Color selection methods",
|
||||
"picker": "Color Wheel",
|
||||
"rgb": "RGB sliders",
|
||||
"quick": "Quick color selectors",
|
||||
"hex": "HEX color input"
|
||||
},
|
||||
"pcmbot": "Show bottom tab bar in PC mode",
|
||||
"pid": "Show preset IDs"
|
||||
"labels":"Show button labels",
|
||||
"colors":{
|
||||
"LABEL":"Color selection methods",
|
||||
"picker": "Color Wheel",
|
||||
"rgb": "RGB sliders",
|
||||
"quick": "Quick color selectors",
|
||||
"hex": "HEX color input"
|
||||
},
|
||||
"pcmbot": "Show bottom tab bar in PC mode",
|
||||
"pid": "Show preset IDs",
|
||||
"seglen": "Set segment length instead of stop LED",
|
||||
"css": "Enable custom CSS",
|
||||
"hdays": "Enable custom Holidays list"
|
||||
},
|
||||
"theme":{
|
||||
"alpha": {
|
||||
@ -34,7 +37,6 @@
|
||||
"bg":"BG HEX color"
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
function gId(s)
|
||||
{
|
||||
@ -52,10 +54,18 @@
|
||||
if( !tar[elem] ) tar[elem] = {}
|
||||
tar = tar[elem];
|
||||
}
|
||||
|
||||
tar[pList[len-1]] = val;
|
||||
}
|
||||
|
||||
var timeout;
|
||||
function showToast(text, error = false)
|
||||
{
|
||||
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 addRec(s, path = "", label = null)
|
||||
{
|
||||
var str = "";
|
||||
@ -181,12 +191,20 @@
|
||||
gId("theme_bg_random").checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
function uploadFile(fO,name) {
|
||||
var req = new XMLHttpRequest();
|
||||
req.addEventListener('load', function(){showToast(this.responseText)});
|
||||
req.addEventListener('error', function(e){showToast(e.stack,true);});
|
||||
req.open("POST", "/upload");
|
||||
var formData = new FormData();
|
||||
formData.append("data", fO.files[0], name);
|
||||
req.send(formData);
|
||||
fO.value = '';
|
||||
return false;
|
||||
}
|
||||
function GetV(){var d=document;}
|
||||
</script>
|
||||
<style>
|
||||
@import url("style.css");
|
||||
</style>
|
||||
<style>@import url("style.css");</style>
|
||||
</head>
|
||||
<body onload="S()">
|
||||
<form id="form_s" name="Sf" method="post">
|
||||
@ -198,7 +216,7 @@
|
||||
</div>
|
||||
<h2>Web Setup</h2>
|
||||
Server description: <input name="DS" maxlength="32"><br>
|
||||
Sync button toggles both send and receive: <input type="checkbox" name="ST"><br>
|
||||
Sync button toggles both send and receive: <input type="checkbox" name="ST"><br>
|
||||
<i>The following UI customization settings are unique both to the WLED device and this browser.<br>
|
||||
You will need to set them again if using a different browser, device or WLED IP address.<br>
|
||||
Refresh the main UI to apply changes.</i><br>
|
||||
@ -207,8 +225,9 @@
|
||||
|
||||
<h3>UI Appearance</h3>
|
||||
<span class="l"></span>: <input type="checkbox" id="comp_labels" class="agi cb"><br>
|
||||
<span class="l"></span>: <input type="checkbox" id="comp_pcmbot" class="agi cb"><br>
|
||||
<span class="l"></span>: <input type="checkbox" id="comp_pid" class="agi cb"><br>
|
||||
<span class="l"></span>: <input type="checkbox" id="comp_pcmbot" class="agi cb"><br>
|
||||
<span class="l"></span>: <input type="checkbox" id="comp_pid" class="agi cb"><br>
|
||||
<span class="l"></span>: <input type="checkbox" id="comp_seglen" class="agi cb"><br>
|
||||
I hate dark mode: <input type="checkbox" id="dm" onchange="UI()"><br>
|
||||
<span id="idonthateyou" style="display:none"><i>Why would you? </i>🥺<br></span>
|
||||
<span class="l"></span>: <input type="number" min=0.0 max=1.0 step=0.01 id="theme_alpha_tab" class="agi"><br>
|
||||
@ -217,6 +236,11 @@
|
||||
<span class="l">BG image URL</span>: <input id="theme_bg_url" class="agi" oninput="checkRandomBg()"><br>
|
||||
<span class="l">Random BG image</span>: <input type="checkbox" id="theme_bg_random" class="agi cb" onchange="setRandomBg()"><br>
|
||||
<input id="theme_base" class="agi" style="display:none">
|
||||
<span class="l"></span>: <input type="checkbox" id="comp_css" class="agi cb"><br>
|
||||
<div id="skin">Custom CSS: <input type="file" name="data" accept=".css"> <input type="button" value="Upload" onclick="uploadFile(d.Sf.data,'/skin.css');"><br></div>
|
||||
<span class="l"></span>: <input type="checkbox" id="comp_hdays" class="agi cb"><br>
|
||||
<div id="holidays">Holidays: <input type="file" name="data2" accept=".json"> <input type="button" value="Upload" onclick="uploadFile(d.Sf.data2,'/holidays.json');"><br></div>
|
||||
<div id="toast"></div>
|
||||
<hr><button type="button" onclick="B()">Back</button><button type="button" onclick="Save()">Save</button>
|
||||
</form>
|
||||
</body>
|
||||
|
@ -9,16 +9,20 @@ body {
|
||||
hr {
|
||||
border-color: #666;
|
||||
}
|
||||
button {
|
||||
button, .btn {
|
||||
background: #333;
|
||||
color: #fff;
|
||||
font-family: Verdana, sans-serif;
|
||||
border: 0.3ch solid #333;
|
||||
display: inline-block;
|
||||
font-size: 20px;
|
||||
margin: 8px;
|
||||
margin-top: 12px;
|
||||
margin: 12px 8px 8px;
|
||||
padding: 1px 6px;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.lnk {
|
||||
border: 0;
|
||||
}
|
||||
.helpB {
|
||||
text-align: left;
|
||||
@ -42,16 +46,16 @@ input[type="number"].xl {
|
||||
width: 85px;
|
||||
}
|
||||
input[type="number"].l {
|
||||
width: 60px;
|
||||
width: 63px;
|
||||
}
|
||||
input[type="number"].m {
|
||||
width: 55px;
|
||||
width: 56px;
|
||||
}
|
||||
input[type="number"].s {
|
||||
width: 42px;
|
||||
width: 49px;
|
||||
}
|
||||
input[type="number"].xs {
|
||||
width: 35px;
|
||||
width: 42px;
|
||||
}
|
||||
input[type="checkbox"] {
|
||||
transform: scale(1.5);
|
||||
@ -69,3 +73,32 @@ td {
|
||||
.d5 {
|
||||
width: 4.5em !important;
|
||||
}
|
||||
|
||||
#toast {
|
||||
opacity: 0;
|
||||
background-color: #444;
|
||||
border-radius: 5px;
|
||||
bottom: 64px;
|
||||
color: #fff;
|
||||
font-size: 17px;
|
||||
padding: 16px;
|
||||
pointer-events: none;
|
||||
position: fixed;
|
||||
text-align: center;
|
||||
z-index: 5;
|
||||
transform: translateX(-50%%); /* %% because of AsyncWebServer */
|
||||
max-width: 90%%; /* %% because of AsyncWebServer */
|
||||
left: 50%%; /* %% because of AsyncWebServer */
|
||||
}
|
||||
|
||||
#toast.show {
|
||||
opacity: 1;
|
||||
background-color: #264;
|
||||
animation: fadein 0.5s, fadein 0.5s 2.5s reverse;
|
||||
}
|
||||
|
||||
#toast.error {
|
||||
opacity: 1;
|
||||
background-color: #b21;
|
||||
animation: fadein 0.5s;
|
||||
}
|
||||
|
@ -379,7 +379,7 @@ String getContentType(AsyncWebServerRequest* request, String filename){
|
||||
if(request->hasArg("download")) return "application/octet-stream";
|
||||
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(".css")) return "text/css";
|
||||
// else if(filename.endsWith(".js")) return "application/javascript";
|
||||
else if(filename.endsWith(".json")) return "application/json";
|
||||
else if(filename.endsWith(".png")) return "image/png";
|
||||
|
File diff suppressed because one or more lines are too long
1615
wled00/html_ui.h
1615
wled00/html_ui.h
File diff suppressed because it is too large
Load Diff
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
// version code in format yymmddb (b = daily build)
|
||||
#define VERSION 2107100
|
||||
#define VERSION 2107230
|
||||
|
||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||
//#define WLED_USE_MY_CONFIG
|
||||
|
@ -15,6 +15,22 @@ bool isIp(String str) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final){
|
||||
if(!index){
|
||||
request->_tempFile = WLED_FS.open(filename, "w");
|
||||
DEBUG_PRINT("Uploading ");
|
||||
DEBUG_PRINTLN(filename);
|
||||
if (filename == "/presets.json") presetsModifiedTime = toki.second();
|
||||
}
|
||||
if (len) {
|
||||
request->_tempFile.write(data,len);
|
||||
}
|
||||
if(final){
|
||||
request->_tempFile.close();
|
||||
request->send(200, "text/plain", F("File Uploaded!"));
|
||||
}
|
||||
}
|
||||
|
||||
bool captivePortal(AsyncWebServerRequest *request)
|
||||
{
|
||||
if (ON_STA_FILTER(request)) return false; //only serve captive in AP mode
|
||||
@ -95,7 +111,12 @@ void initServer()
|
||||
const String& url = request->url();
|
||||
isConfig = url.indexOf("cfg") > -1;
|
||||
if (!isConfig) {
|
||||
fileDoc = &jsonBuffer;
|
||||
#ifdef WLED_DEBUG
|
||||
DEBUG_PRINTLN(F("Serialized HTTP"));
|
||||
serializeJson(root,Serial);
|
||||
DEBUG_PRINTLN();
|
||||
#endif
|
||||
fileDoc = &jsonBuffer; // used for applying presets (presets.cpp)
|
||||
verboseResponse = deserializeState(root);
|
||||
fileDoc = nullptr;
|
||||
} else {
|
||||
@ -137,6 +158,11 @@ void initServer()
|
||||
serveMessage(request, 418, F("418. I'm a teapot."), F("(Tangible Embedded Advanced Project Of Twinkling)"), 254);
|
||||
});
|
||||
|
||||
server.on("/upload", HTTP_POST, [](AsyncWebServerRequest *request) {},
|
||||
[](AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data,
|
||||
size_t len, bool final) {handleUpload(request, filename, index, data, len, final);}
|
||||
);
|
||||
|
||||
//if OTA is allowed
|
||||
if (!otaLock){
|
||||
#ifdef WLED_ENABLE_FS_EDITOR
|
||||
@ -197,15 +223,15 @@ void initServer()
|
||||
}
|
||||
|
||||
|
||||
#ifdef WLED_ENABLE_DMX
|
||||
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor);
|
||||
});
|
||||
#else
|
||||
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254);
|
||||
});
|
||||
#endif
|
||||
#ifdef WLED_ENABLE_DMX
|
||||
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
request->send_P(200, "text/html", PAGE_dmxmap , dmxProcessor);
|
||||
});
|
||||
#else
|
||||
server.on("/dmxmap", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
serveMessage(request, 501, "Not implemented", F("DMX support is not enabled in this build."), 254);
|
||||
});
|
||||
#endif
|
||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||
if (captivePortal(request)) return;
|
||||
serveIndexOrWelcome(request);
|
||||
@ -261,7 +287,13 @@ bool handleIfNoneMatchCacheHeader(AsyncWebServerRequest* request)
|
||||
|
||||
void setStaticContentCacheHeaders(AsyncWebServerResponse *response)
|
||||
{
|
||||
#ifndef WLED_DEBUG
|
||||
//this header name is misleading, "no-cache" will not disable cache,
|
||||
//it just revalidates on every load using the "If-None-Match" header with the last ETag value
|
||||
response->addHeader(F("Cache-Control"),"no-cache");
|
||||
#else
|
||||
response->addHeader(F("Cache-Control"),"no-store,max-age=0"); // prevent caching if debug build
|
||||
#endif
|
||||
response->addHeader(F("ETag"), String(VERSION));
|
||||
}
|
||||
|
||||
@ -275,7 +307,6 @@ void serveIndex(AsyncWebServerRequest* request)
|
||||
|
||||
response->addHeader(F("Content-Encoding"),"gzip");
|
||||
setStaticContentCacheHeaders(response);
|
||||
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user