kopia lustrzana https://github.com/Aircoookie/WLED
				
				
				
			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>pull/2101/head
							rodzic
							
								
									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
								
								
								
								
							
										
											
												Plik diff jest za duży
												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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -136,7 +157,12 @@ void initServer()
 | 
			
		|||
  server.on("/teapot", HTTP_GET, [](AsyncWebServerRequest *request){
 | 
			
		||||
    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);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue