Use sequential loading and requests for all UI resources (#5013)

* use sequential loading for all UI resources

- load common.js and style.css sequentially for all config pages
- restrict all requrests in index.js to single connection
- retry more than once if requests fail
- incremental timeouts to make them faster and still more robust
- bugfix in connectWs()
- on page load, presets are loaded from localStorage if controller was not rebooted
- remove hiding of segment freeze button when not collapsed
This commit is contained in:
Damian Schneider 2025-12-14 10:13:00 +01:00 committed by GitHub
parent d1260ccf8b
commit 32b104e1a9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 401 additions and 342 deletions

View File

@ -51,6 +51,38 @@ function tooltip(cont=null) {
}); });
}); });
}; };
// sequential loading of external resources (JS or CSS) with retry, calls init() when done
function loadResources(files, init) {
let i = 0;
const loadNext = () => {
if (i >= files.length) {
if (init) {
d.documentElement.style.visibility = 'visible'; // make page visible after all files are loaded if it was hidden (prevent ugly display)
d.readyState === 'complete' ? init() : window.addEventListener('load', init);
}
return;
}
const file = files[i++];
const isCSS = file.endsWith('.css');
const el = d.createElement(isCSS ? 'link' : 'script');
if (isCSS) {
el.rel = 'stylesheet';
el.href = file;
const st = d.head.querySelector('style');
if (st) d.head.insertBefore(el, st); // insert before any <style> to allow overrides
else d.head.appendChild(el);
} else {
el.src = file;
d.head.appendChild(el);
}
el.onload = () => { loadNext(); };
el.onerror = () => {
i--; // load this file again
setTimeout(loadNext, 100);
};
};
loadNext();
}
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript // https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true, preGetV = undefined, postGetV = undefined) { function loadJS(FILE_URL, async = true, preGetV = undefined, postGetV = undefined) {
let scE = d.createElement("script"); let scE = d.createElement("script");
@ -116,21 +148,22 @@ function uploadFile(fileObj, name) {
fileObj.value = ''; fileObj.value = '';
return false; return false;
} }
// connect to WebSocket, use parent WS or open new // connect to WebSocket, use parent WS or open new, callback function gets passed the new WS object
function connectWs(onOpen) { function connectWs(onOpen) {
try { let ws;
if (top.window.ws && top.window.ws.readyState === WebSocket.OPEN) { try { ws = top.window.ws;} catch (e) {}
if (onOpen) onOpen(); // reuse if open
return top.window.ws; if (ws && ws.readyState === WebSocket.OPEN) {
} if (onOpen) onOpen(ws);
} catch (e) {} } else {
// create new ws connection
getLoc(); // ensure globals (loc, locip, locproto) are up to date getLoc(); // ensure globals are up to date
let url = loc ? getURL('/ws').replace("http","ws") : "ws://"+window.location.hostname+"/ws"; let url = loc ? getURL('/ws').replace("http", "ws")
let ws = new WebSocket(url); : "ws://" + window.location.hostname + "/ws";
ws = new WebSocket(url);
ws.binaryType = "arraybuffer"; ws.binaryType = "arraybuffer";
if (onOpen) { ws.onopen = onOpen; } if (onOpen) ws.onopen = () => onOpen(ws);
try { top.window.ws = ws; } catch (e) {} // store in parent for reuse }
return ws; return ws;
} }

View File

@ -1476,7 +1476,7 @@ dialog {
.expanded { .expanded {
display: inline-block !important; display: inline-block !important;
} }
.hide, .expanded .segin.hide, .expanded .presin.hide, .expanded .sbs.hide, .expanded .frz, .expanded .g-icon { .hide, .expanded .segin.hide, .expanded .presin.hide, .expanded .sbs.hide, .expanded .g-icon {
display: none !important; display: none !important;
} }

View File

@ -16,13 +16,12 @@ var simplifiedUI = false;
var tr = 7; var tr = 7;
var d = document; var d = document;
const ranges = RangeTouch.setup('input[type="range"]', {}); const ranges = RangeTouch.setup('input[type="range"]', {});
var retry = false;
var palettesData; var palettesData;
var fxdata = []; var fxdata = [];
var pJson = {}, eJson = {}, lJson = {}; var pJson = {}, eJson = {}, lJson = {};
var plJson = {}; // array of playlists var plJson = {}; // array of playlists
var pN = "", pI = 0, pNum = 0; var pN = "", pI = 0, pNum = 0;
var pmt = 1, pmtLS = 0, pmtLast = 0; var pmt = 1, pmtLS = 0;
var lastinfo = {}; var lastinfo = {};
var isM = false, mw = 0, mh=0; var isM = false, mw = 0, mh=0;
var ws, wsRpt=0; var ws, wsRpt=0;
@ -200,19 +199,17 @@ function loadBg() {
}); });
} }
function loadSkinCSS(cId) function loadSkinCSS(cId) {
{ return new Promise((resolve, reject) => {
if (!gId(cId)) // check if element exists if (gId(cId)) return resolve();
{ const l = d.createElement('link');
var h = d.getElementsByTagName('head')[0];
var l = d.createElement('link');
l.id = cId; l.id = cId;
l.rel = 'stylesheet'; l.rel = 'stylesheet';
l.type = 'text/css';
l.href = getURL('/skin.css'); l.href = getURL('/skin.css');
l.media = 'all'; l.onload = resolve;
h.appendChild(l); l.onerror = reject;
} d.head.appendChild(l);
});
} }
function getURL(path) { function getURL(path) {
@ -278,19 +275,23 @@ function onLoad()
cpick.on("color:change", () => {updatePSliders()}); cpick.on("color:change", () => {updatePSliders()});
pmtLS = localStorage.getItem('wledPmt'); pmtLS = localStorage.getItem('wledPmt');
// Load initial data // Load initial data sequentially, no parallel requests to avoid "503" errors when heap is low (slower but much more reliable)
loadPalettes(()=>{ (async ()=>{
// fill effect extra data array try {
loadFXData(()=>{ await loadPalettes(); // loads base palettes and builds #pallist (safe first)
// load and populate effects await loadFXData(); // loads fx data
setTimeout(()=>{loadFX(()=>{ await loadFX(); // populates effect list
loadPalettesData(()=>{ await requestJson(); // updates info variables
requestJson();// will load presets and create WS await loadPalettesData(); // fills palettesData[] for previews
if (cfg.comp.css) setTimeout(()=>{loadSkinCSS('skinCss')},50); populatePalettes(); // repopulate with custom palettes now that cpalcount is known
}); if(pmt == pmtLS) populatePresets(true); // load presets from localStorage if signature matches (i.e. no device reboot)
})},50); else await loadPresets(); // load and populate presets
}); if (cfg.comp.css) await loadSkinCSS('skinCss');
}); if (!ws) makeWS();
} catch(e) {
showToast("Init failed: " + e, true);
}
})();
resetUtil(); resetUtil();
d.addEventListener("visibilitychange", handleVisibilityChange, false); d.addEventListener("visibilitychange", handleVisibilityChange, false);
@ -448,7 +449,7 @@ function presetError(empty)
if (bckstr.length > 10) hasBackup = true; if (bckstr.length > 10) hasBackup = true;
} catch (e) {} } catch (e) {}
var cn = `<div class="pres c" style="padding:8px;margin-bottom:8px;${empty?'':'cursor:pointer;'}" ${empty?'':'onclick="pmtLast=0;loadPresets();"'}>`; var cn = `<div class="pres c" style="padding:8px;margin-bottom:8px;${empty?'':'cursor:pointer;'}" ${empty?'':'onclick="loadPresets();"'}>`;
if (empty) if (empty)
cn += `You have no presets yet!`; cn += `You have no presets yet!`;
else else
@ -481,123 +482,81 @@ function restore(txt) {
return false; return false;
} }
function loadPresets(callback = null) async function loadPresets() {
{ return new Promise((resolve) => {
// 1st boot (because there is a callback) fetch(getURL('/presets.json'), {method: 'get'})
if (callback && pmt == pmtLS && pmt > 0) { .then(res => res.status=="404" ? {"0":{}} : res.json())
// we have a copy of the presets in local storage and don't need to fetch another one
populatePresets(true);
pmtLast = pmt;
callback();
return;
}
// afterwards
if (!callback && pmt == pmtLast) return;
fetch(getURL('/presets.json'), {
method: 'get'
})
.then(res => {
if (res.status=="404") return {"0":{}};
//if (!res.ok) showErrorToast();
return res.json();
})
.then(json => { .then(json => {
pJson = json; pJson = json;
pmtLast = pmt;
populatePresets(); populatePresets();
resolve();
}) })
.catch((e)=>{ .catch(() => {
//showToast(e, true);
presetError(false); presetError(false);
resolve();
}) })
.finally(()=>{
if (callback) setTimeout(callback,99);
}); });
} }
function loadPalettes(callback = null) async function loadPalettes(retry=0) {
{ return new Promise((resolve) => {
fetch(getURL('/json/palettes'), { fetch(getURL('/json/palettes'), {method: 'get'})
method: 'get' .then(res => res.ok ? res.json() : Promise.reject())
}) .then(json => {
.then((res)=>{
if (!res.ok) showErrorToast();
return res.json();
})
.then((json)=>{
lJson = Object.entries(json); lJson = Object.entries(json);
populatePalettes(); populatePalettes();
retry = false; resolve();
}) })
.catch((e)=>{ .catch((e) => {
if (!retry) { if (retry<5) {
retry = true; setTimeout(() => loadPalettes(retry+1).then(resolve), 100);
setTimeout(loadPalettes, 500); // retry } else {
}
showToast(e, true); showToast(e, true);
}) resolve();
.finally(()=>{ }
if (callback) callback(); });
updateUI();
}); });
} }
function loadFX(callback = null) async function loadFX(retry=0) {
{ return new Promise((resolve) => {
fetch(getURL('/json/effects'), { fetch(getURL('/json/effects'), {method: 'get'})
method: 'get' .then(res => res.ok ? res.json() : Promise.reject())
}) .then(json => {
.then((res)=>{
if (!res.ok) showErrorToast();
return res.json();
})
.then((json)=>{
eJson = Object.entries(json); eJson = Object.entries(json);
populateEffects(); populateEffects();
retry = false; resolve();
}) })
.catch((e)=>{ .catch((e) => {
if (!retry) { if (retry<5) {
retry = true; setTimeout(() => loadFX(retry+1).then(resolve), 100);
setTimeout(loadFX, 500); // retry } else {
}
showToast(e, true); showToast(e, true);
}) resolve();
.finally(()=>{ }
if (callback) callback(); });
updateUI();
}); });
} }
function loadFXData(callback = null) async function loadFXData(retry=0) {
{ return new Promise((resolve) => {
fetch(getURL('/json/fxdata'), { fetch(getURL('/json/fxdata'), {method: 'get'})
method: 'get' .then(res => res.ok ? res.json() : Promise.reject())
}) .then(json => {
.then((res)=>{
if (!res.ok) showErrorToast();
return res.json();
})
.then((json)=>{
fxdata = json||[]; fxdata = json||[];
// add default value for Solid fxdata.shift();
fxdata.shift()
fxdata.unshift(";!;"); fxdata.unshift(";!;");
retry = false; resolve();
}) })
.catch((e)=>{ .catch((e) => {
fxdata = []; fxdata = [];
if (!retry) { if (retry<5) {
retry = true; setTimeout(() => loadFXData(retry+1).then(resolve), 100);
setTimeout(()=>{loadFXData(loadFX);}, 500); // retry } else {
}
showToast(e, true); showToast(e, true);
}) resolve();
.finally(()=>{ }
if (callback) callback(); });
updateUI();
}); });
} }
@ -619,7 +578,7 @@ function populateQL()
function populatePresets(fromls) function populatePresets(fromls)
{ {
if (fromls) pJson = JSON.parse(localStorage.getItem("wledP")); if (fromls) pJson = JSON.parse(localStorage.getItem("wledP"));
if (!pJson) {setTimeout(loadPresets,250); return;} if (!pJson) {loadPresets(); return;} // note: no await as this is a fallback that should not be needed as init function fetches pJson
delete pJson["0"]; delete pJson["0"];
var cn = ""; var cn = "";
var arr = Object.entries(pJson).sort(cmpP); var arr = Object.entries(pJson).sort(cmpP);
@ -701,10 +660,10 @@ function parseInfo(i) {
//var setInnerHTML = function(elm, html) { //var setInnerHTML = function(elm, html) {
// elm.innerHTML = html; // elm.innerHTML = html;
// Array.from(elm.querySelectorAll("script")).forEach( oldScript => { // Array.from(elm.querySelectorAll("script")).forEach( oldScript => {
// const newScript = document.createElement("script"); // const newScript = d.createElement("script");
// Array.from(oldScript.attributes) // Array.from(oldScript.attributes)
// .forEach( attr => newScript.setAttribute(attr.name, attr.value) ); // .forEach( attr => newScript.setAttribute(attr.name, attr.value) );
// newScript.appendChild(document.createTextNode(oldScript.innerHTML)); // newScript.appendChild(d.createTextNode(oldScript.innerHTML));
// oldScript.parentNode.replaceChild(newScript, oldScript); // oldScript.parentNode.replaceChild(newScript, oldScript);
// }); // });
//} //}
@ -906,6 +865,7 @@ function populateSegments(s)
gId("segcont").classList.remove("hide"); gId("segcont").classList.remove("hide");
let noNewSegs = (lowestUnused >= maxSeg); let noNewSegs = (lowestUnused >= maxSeg);
resetUtil(noNewSegs); resetUtil(noNewSegs);
if (segCount === 0) return; // no segments to populate
for (var i = 0; i <= lSeg; i++) { for (var i = 0; i <= lSeg; i++) {
if (!gId(`seg${i}`)) continue; if (!gId(`seg${i}`)) continue;
updateLen(i); updateLen(i);
@ -1437,7 +1397,7 @@ function makeWS() {
}; };
ws.onclose = (e)=>{ ws.onclose = (e)=>{
gId('connind').style.backgroundColor = "var(--c-r)"; gId('connind').style.backgroundColor = "var(--c-r)";
if (wsRpt++ < 5) setTimeout(makeWS,1500); // retry WS connection if (wsRpt++ < 10) setTimeout(makeWS,wsRpt * 200); // retry WS connection
ws = null; ws = null;
} }
ws.onopen = (e)=>{ ws.onopen = (e)=>{
@ -1470,6 +1430,7 @@ function readState(s,command=false)
populateSegments(s); populateSegments(s);
hasRGB = hasWhite = hasCCT = has2D = false; hasRGB = hasWhite = hasCCT = has2D = false;
segLmax = 0; // reset max selected segment length
let i = {}; let i = {};
// determine light capabilities from selected segments // determine light capabilities from selected segments
for (let seg of (s.seg||[])) { for (let seg of (s.seg||[])) {
@ -1709,77 +1670,68 @@ function setEffectParameters(idx)
var jsonTimeout; var jsonTimeout;
var reqsLegal = false; var reqsLegal = false;
async function requestJson(command=null, retry=0) {
function requestJson(command=null) return new Promise((resolve, reject) => {
{
gId('connind').style.backgroundColor = "var(--c-y)"; gId('connind').style.backgroundColor = "var(--c-y)";
if (command && !reqsLegal) return; // stop post requests from chrome onchange event on page restore if (command && !reqsLegal) {resolve(); return;}
if (!jsonTimeout) jsonTimeout = setTimeout(()=>{if (ws) ws.close(); ws=null; showErrorToast()}, 3000); if (!jsonTimeout) jsonTimeout = setTimeout(()=>{if (ws) ws.close(); ws=null; showErrorToast()}, 3000);
var req = null;
var useWs = (ws && ws.readyState === WebSocket.OPEN); var useWs = (ws && ws.readyState === WebSocket.OPEN);
var type = command ? 'post':'get'; var req = null;
if (command) { if (command) {
command.v = true; // force complete /json/si API response command.v = true;
command.time = Math.floor(Date.now() / 1000); command.time = Math.floor(Date.now() / 1000);
var t = gId('tt'); var t = gId('tt');
if (t.validity.valid && command.transition==null) { if (t && t.validity.valid && command.transition==null) {
var tn = parseInt(t.value*10); var tn = parseInt(t.value*10);
if (tn != tr) command.transition = tn; if (tn != tr) command.transition = tn;
} }
//command.bs = parseInt(gId('bs').value);
req = JSON.stringify(command); req = JSON.stringify(command);
if (req.length > 1340) useWs = false; // do not send very long requests over websocket if (req.length > 1340) useWs = false;
if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false; // esp8266 can only handle 500 bytes if (req.length > 500 && lastinfo && lastinfo.arch == "esp8266") useWs = false;
}; }
if (useWs) { if (useWs) {
ws.send(req?req:'{"v":true}'); ws.send(req?req:'{"v":true}');
resolve();
return; return;
} }
fetch(getURL('/json/si'), { fetch(getURL('/json/si'), {
method: type, method: command ? 'post' : 'get',
headers: {"Content-Type": "application/json; charset=UTF-8"}, headers: {"Content-Type": "application/json; charset=UTF-8"},
body: req body: req
}) })
.then(res => { .then(res => {
clearTimeout(jsonTimeout); clearTimeout(jsonTimeout);
jsonTimeout = null; jsonTimeout = null;
if (!res.ok) showErrorToast(); return res.ok ? res.json() : Promise.reject();
return res.json();
}) })
.then(json => { .then(json => {
lastUpdate = new Date(); lastUpdate = new Date();
clearErrorToast(3000); clearErrorToast(3000);
gId('connind').style.backgroundColor = "var(--c-g)"; gId('connind').style.backgroundColor = "var(--c-g)";
if (!json) { showToast('Empty response', true); return; } if (!json) { showToast('Empty response', true); resolve(); return; }
if (json.success) return; if (json.success) {resolve(); return;}
if (json.info) { if (json.info) {
let i = json.info; parseInfo(json.info);
parseInfo(i); if (isInfo) populateInfo(json.info);
populatePalettes(i);
if (isInfo) populateInfo(i);
if (simplifiedUI) simplifyUI(); if (simplifiedUI) simplifyUI();
} }
var s = json.state ? json.state : json; var s = json.state ? json.state : json;
readState(s); readState(s);
//load presets and open websocket sequentially
if (!pJson || isEmpty(pJson)) setTimeout(()=>{
loadPresets(()=>{
wsRpt = 0;
if (!(ws && ws.readyState === WebSocket.OPEN)) makeWS();
});
},25);
reqsLegal = true; reqsLegal = true;
retry = false; resolve();
}) })
.catch((e)=>{ .catch((e)=>{
if (!retry) { if (retry<10) {
retry = true; setTimeout(() => requestJson(command,retry+1).then(resolve).catch(reject), retry*50);
setTimeout(requestJson,500); } else {
}
showToast(e, true); showToast(e, true);
resolve();
}
});
}); });
} }
@ -2554,7 +2506,7 @@ function saveP(i,pl)
} }
populatePresets(); populatePresets();
resetPUtil(); resetPUtil();
setTimeout(()=>{pmtLast=0; loadPresets();}, 750); // force reloading of presets setTimeout(()=>{loadPresets();}, 750); // force reloading of presets
} }
function testPl(i,bt) { function testPl(i,bt) {
@ -2820,56 +2772,51 @@ function rSegs()
requestJson(obj); requestJson(obj);
} }
function loadPalettesData(callback = null) function loadPalettesData() {
{ return new Promise((resolve) => {
if (palettesData) return; if (palettesData) return resolve(); // already loaded
const lsKey = "wledPalx"; var lsPalData = localStorage.getItem("wledPalx");
var lsPalData = localStorage.getItem(lsKey);
if (lsPalData) { if (lsPalData) {
try { try {
var d = JSON.parse(lsPalData); var d = JSON.parse(lsPalData);
if (d && d.vid == d.vid) { if (d && d.vid == lastinfo.vid) {
palettesData = d.p; palettesData = d.p;
if (callback) callback(); redrawPalPrev();
return; return resolve();
} }
} catch (e) {} } catch (e) {}
} }
palettesData = {}; palettesData = {};
getPalettesData(0, ()=>{ getPalettesData(0, () => {
localStorage.setItem(lsKey, JSON.stringify({ localStorage.setItem("wledPalx", JSON.stringify({
p: palettesData, p: palettesData,
vid: lastinfo.vid vid: lastinfo.vid
})); }));
redrawPalPrev(); redrawPalPrev();
if (callback) setTimeout(callback, 99); setTimeout(resolve, 99); // delay optional
});
}); });
} }
function getPalettesData(page, callback) function getPalettesData(page, callback, retry=0) {
{ fetch(getURL(`/json/palx?page=${page}`), {method: 'get'})
fetch(getURL(`/json/palx?page=${page}`), { .then(res => res.ok ? res.json() : Promise.reject())
method: 'get'
})
.then(res => {
if (!res.ok) showErrorToast();
return res.json();
})
.then(json => { .then(json => {
retry = false;
palettesData = Object.assign({}, palettesData, json.p); palettesData = Object.assign({}, palettesData, json.p);
if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75); if (page < json.m) setTimeout(()=>{ getPalettesData(page + 1, callback); }, 75);
else callback(); else callback();
}) })
.catch((error)=>{ .catch((error)=>{
if (!retry) { if (retry<5) {
retry = true; setTimeout(()=>{getPalettesData(page,callback,retry+1);}, 100);
setTimeout(()=>{getPalettesData(page,callback);}, 500); // retry } else {
}
showToast(error, true); showToast(error, true);
callback();
}
}); });
} }
/* /*
function hideModes(txt) function hideModes(txt)
{ {
@ -2971,7 +2918,7 @@ function filterFocus(e) {
} }
if (e.type === "blur") { if (e.type === "blur") {
setTimeout(() => { setTimeout(() => {
if (e.target === document.activeElement && document.hasFocus()) return; if (e.target === d.activeElement && d.hasFocus()) return;
// do not hide if filter is active // do not hide if filter is active
if (!c) { if (!c) {
// compute sticky top // compute sticky top
@ -3218,7 +3165,7 @@ function simplifyUI() {
// Create dropdown dialog // Create dropdown dialog
function createDropdown(id, buttonText, dialogElements = null) { function createDropdown(id, buttonText, dialogElements = null) {
// Create dropdown dialog // Create dropdown dialog
const dialog = document.createElement("dialog"); const dialog = d.createElement("dialog");
// Move every dialogElement to the dropdown dialog or if none are given, move all children of the element with the given id // Move every dialogElement to the dropdown dialog or if none are given, move all children of the element with the given id
if (dialogElements) { if (dialogElements) {
dialogElements.forEach((e) => { dialogElements.forEach((e) => {
@ -3231,7 +3178,7 @@ function simplifyUI() {
} }
// Create button for the dropdown // Create button for the dropdown
const btn = document.createElement("button"); const btn = d.createElement("button");
btn.id = id + "btn"; btn.id = id + "btn";
btn.classList.add("btn"); btn.classList.add("btn");
btn.innerText = buttonText; btn.innerText = buttonText;
@ -3279,7 +3226,7 @@ function simplifyUI() {
// Hide palette label // Hide palette label
gId("pall").style.display = "none"; gId("pall").style.display = "none";
gId("Colors").insertBefore(document.createElement("br"), gId("pall")); gId("Colors").insertBefore(d.createElement("br"), gId("pall"));
// Hide effect label // Hide effect label
gId("modeLabel").style.display = "none"; gId("modeLabel").style.display = "none";
@ -3291,7 +3238,7 @@ function simplifyUI() {
// Hide bottom bar // Hide bottom bar
gId("bot").style.display = "none"; gId("bot").style.display = "none";
document.documentElement.style.setProperty('--bh', '0px'); d.documentElement.style.setProperty('--bh', '0px');
// Hide other tabs // Hide other tabs
gId("Effects").style.display = "none"; gId("Effects").style.display = "none";

View File

@ -17,8 +17,15 @@
position: absolute; position: absolute;
} }
</style> </style>
<script src="common.js"></script>
<script> <script>
// load common.js with retry on error
(function common() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => S();
l.onerror = () => setTimeout(common, 100);
document.head.appendChild(l);
})();
var ws; var ws;
var tmout = null; var tmout = null;
var c; var c;
@ -31,7 +38,7 @@
ctx.fillRect(Math.round((i - start) * w / skip), 0, Math.ceil(w), c.height); ctx.fillRect(Math.round((i - start) * w / skip), 0, Math.ceil(w), c.height);
} }
} }
function update() { // via HTTP (/json/live) function update(retry=0) { // via HTTP (/json/live)
if (d.hidden) { if (d.hidden) {
clearTimeout(tmout); clearTimeout(tmout);
tmout = setTimeout(update, 250); tmout = setTimeout(update, 250);
@ -53,7 +60,7 @@
.catch((error)=>{ .catch((error)=>{
//console.error("Peek HTTP error:",error); //console.error("Peek HTTP error:",error);
clearTimeout(tmout); clearTimeout(tmout);
tmout = setTimeout(update, 2500); if (retry<5) tmout = setTimeout(() => update(retry+1), 2500); // stop endlessly bugging the ESP if resource is not available
}) })
} }
function S() { // Startup function (onload) function S() { // Startup function (onload)
@ -62,10 +69,7 @@
if (window.location.href.indexOf("?ws") == -1) {update(); return;} if (window.location.href.indexOf("?ws") == -1) {update(); return;}
// Initialize WebSocket connection // Initialize WebSocket connection
ws = connectWs(function () { ws = connectWs(ws => ws.send('{"lv":true}'));
//console.info("Peek WS open");
ws.send('{"lv":true}');
});
ws.addEventListener('message', (e) => { ws.addEventListener('message', (e) => {
try { try {
if (toString.call(e.data) === '[object ArrayBuffer]') { if (toString.call(e.data) === '[object ArrayBuffer]') {
@ -81,7 +85,7 @@
} }
</script> </script>
</head> </head>
<body onload="S()"> <body>
<canvas id="canv"></canvas> <canvas id="canv"></canvas>
</body> </body>
</html> </html>

View File

@ -10,11 +10,18 @@
margin: 0; margin: 0;
} }
</style> </style>
<script src="common.js"></script>
</head> </head>
<body> <body>
<canvas id="canv"></canvas> <canvas id="canv"></canvas>
<script> <script>
// load common.js with retry on error
(function common() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => S();
l.onerror = () => setTimeout(common, 100);
document.head.appendChild(l);
})();
var c = document.getElementById('canv'); var c = document.getElementById('canv');
var leds = ""; var leds = "";
var throttled = false; var throttled = false;
@ -22,14 +29,12 @@
c.width = window.innerWidth * 0.98; //remove scroll bars c.width = window.innerWidth * 0.98; //remove scroll bars
c.height = window.innerHeight * 0.98; //remove scroll bars c.height = window.innerHeight * 0.98; //remove scroll bars
} }
function S() { // Startup function (onload)
setCanvas(); setCanvas();
// Check for canvas support // Check for canvas support
var ctx = c.getContext('2d'); var ctx = c.getContext('2d');
if (ctx) { // Access the rendering context if (ctx) { // Access the rendering context
// use parent WS or open new ws = connectWs(ws => ws.send('{"lv":true}')); // use parent WS or open new
var ws = connectWs(()=>{
ws.send('{"lv":true}');
});
ws.addEventListener('message',(e)=>{ ws.addEventListener('message',(e)=>{
try { try {
if (toString.call(e.data) === '[object ArrayBuffer]') { if (toString.call(e.data) === '[object ArrayBuffer]') {
@ -53,6 +58,7 @@
} }
}); });
} }
}
// window.resize event listener // window.resize event listener
window.addEventListener('resize', (e)=>{ window.addEventListener('resize', (e)=>{
if (!throttled) { // only run if we're not throttled if (!throttled) { // only run if we're not throttled

View File

@ -4,12 +4,19 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>WLED Settings</title> <title>WLED Settings</title>
<script src="common.js" type="text/javascript"></script>
<script> <script>
function S() { // load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
// load style.css then initialize
l.onload = () => loadResources(['style.css'], () => {
getLoc(); getLoc();
loadJS(getURL('/settings/s.js?p=0'), false); // If we set async false, file is loaded and executed, then next statement is processed loadJS(getURL('/settings/s.js?p=0'), false);
} });
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
</script> </script>
<style> <style>
@import url("style.css"); @import url("style.css");
@ -31,7 +38,7 @@
} }
</style> </style>
</head> </head>
<body onload="S()"> <body>
<button type=submit id="b" onclick="window.location=getURL('/')">Back</button> <button type=submit id="b" onclick="window.location=getURL('/')">Back</button>
<button type="submit" onclick="window.location=getURL('/settings/wifi')">WiFi Setup</button> <button type="submit" onclick="window.location=getURL('/settings/wifi')">WiFi Setup</button>
<button type="submit" onclick="window.location=getURL('/settings/leds')">LED Preferences</button> <button type="submit" onclick="window.location=getURL('/settings/leds')">LED Preferences</button>

View File

@ -4,11 +4,20 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>2D Set-up</title> <title>2D Set-up</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
var maxPanels=64; var maxPanels=64;
var ctx = null; var ctx = null;
function fS(){d.Sf.submit();} // <button type=submit> sometimes didn't work function fS(){d.Sf.submit();} // <button type=submit> sometimes didn't work
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
function S() { function S() {
getLoc(); getLoc();
loadJS(getURL('/settings/s.js?p=10'), false, undefined, ()=>{ loadJS(getURL('/settings/s.js?p=10'), false, undefined, ()=>{
@ -238,9 +247,8 @@ Y: <input name="P${i}Y" type="number" min="0" max="255" value="0" oninput="UI()"
gId("MD").innerHTML = "Matrix Dimensions (W*H=LC): " + maxWidth + " x " + maxHeight + " = " + maxWidth * maxHeight; gId("MD").innerHTML = "Matrix Dimensions (W*H=LC): " + maxWidth + " x " + maxHeight + " = " + maxWidth * maxHeight;
} }
</script> </script>
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post"> <form id="form_s" name="Sf" method="post">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="H('features/2D')">?</button></div> <div class="helpB"><button type="button" onclick="H('features/2D')">?</button></div>

View File

@ -4,8 +4,16 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8"> <meta charset="utf-8">
<title>DMX Settings</title> <title>DMX Settings</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
function HW(){window.open("https://kno.wled.ge/interfaces/dmx-output/");} function HW(){window.open("https://kno.wled.ge/interfaces/dmx-output/");}
function GCH(num) { function GCH(num) {
gId('dmxchannels').innerHTML += ""; gId('dmxchannels').innerHTML += "";
@ -38,9 +46,8 @@
if (loc) d.Sf.action = getURL('/settings/dmx'); if (loc) d.Sf.action = getURL('/settings/dmx');
} }
</script> </script>
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post"> <form id="form_s" name="Sf" method="post">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="HW()">?</button></div> <div class="helpB"><button type="button" onclick="HW()">?</button></div>

View File

@ -4,7 +4,7 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>LED Settings</title> <title>LED Settings</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxBT=4; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32 var maxB=1,maxD=1,maxA=1,maxV=0,maxM=4000,maxPB=2048,maxL=1664,maxCO=5,maxBT=4; //maximum bytes for LED allocation: 4kB for 8266, 32kB for 32
var customStarts=false,startsDirty=[]; var customStarts=false,startsDirty=[];
@ -26,6 +26,15 @@
function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins
function chrID(x) { return String.fromCharCode((x<10?48:55)+x); } function chrID(x) { return String.fromCharCode((x<10?48:55)+x); }
function toNum(c) { let n=c.charCodeAt(0); return (n>=48 && n<=57)?n-48:(n>=65 && n<=90)?n-55:0; } // convert char (0-9A-Z) to number (0-35) function toNum(c) { let n=c.charCodeAt(0); return (n>=48 && n<=57)?n-48:(n>=65 && n<=90)?n-55:0; } // convert char (0-9A-Z) to number (0-35)
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
function S() { function S() {
getLoc(); getLoc();
loadJS(getURL('/settings/s.js?p=2'), false, ()=>{ loadJS(getURL('/settings/s.js?p=2'), false, ()=>{
@ -846,9 +855,8 @@ Swap: <select id="xw${s}" name="XW${s}">
}); });
} }
</script> </script>
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post"> <form id="form_s" name="Sf" method="post">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="H('features/settings/#led-settings')">?</button></div> <div class="helpB"><button type="button" onclick="H('features/settings/#led-settings')">?</button></div>

View File

@ -4,8 +4,16 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8"> <meta charset="utf-8">
<title>Misc Settings</title> <title>Misc Settings</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
function U() { window.open(getURL("/update"),"_self"); } function U() { window.open(getURL("/update"),"_self"); }
function checkNum(o) { function checkNum(o) {
const specialkeys = ["Backspace", "Tab", "Enter", "Shift", "Control", "Alt", "Pause", "CapsLock", "Escape", "Space", "PageUp", "PageDown", "End", "Home", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown", "Insert", "Delete"]; const specialkeys = ["Backspace", "Tab", "Enter", "Shift", "Control", "Alt", "Pause", "CapsLock", "Escape", "Space", "PageUp", "PageDown", "End", "Home", "ArrowLeft", "ArrowUp", "ArrowRight", "ArrowDown", "Insert", "Delete"];
@ -30,11 +38,8 @@
if (loc) d.Sf.action = getURL('/settings/sec'); if (loc) d.Sf.action = getURL('/settings/sec');
} }
</script> </script>
<style>
@import url("style.css");
</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post"> <form id="form_s" name="Sf" method="post">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="H('features/settings/#security-settings')">?</button></div> <div class="helpB"><button type="button" onclick="H('features/settings/#security-settings')">?</button></div>

View File

@ -4,8 +4,16 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8"> <meta charset="utf-8">
<title>Sync Settings</title> <title>Sync Settings</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;} function adj(){if (d.Sf.DI.value == 6454) {if (d.Sf.EU.value == 1) d.Sf.EU.value = 0;}
else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} } else if (d.Sf.DI.value == 5568) {if (d.Sf.DA.value == 0) d.Sf.DA.value = 1; if (d.Sf.EU.value == 0) d.Sf.EU.value = 1;} }
function FC() function FC()
@ -43,9 +51,8 @@
function hideDMXInput(){gId("dmxInput").style.display="none";} function hideDMXInput(){gId("dmxInput").style.display="none";}
function hideNoDMXInput(){gId("dmxInputOff").style.display="none";} function hideNoDMXInput(){gId("dmxInputOff").style.display="none";}
</script> </script>
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post" onsubmit="GC()"> <form id="form_s" name="Sf" method="post" onsubmit="GC()">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="H('interfaces/udp-notifier/')">?</button></div> <div class="helpB"><button type="button" onclick="H('interfaces/udp-notifier/')">?</button></div>

View File

@ -4,10 +4,19 @@
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta charset="utf-8"> <meta charset="utf-8">
<title>Time Settings</title> <title>Time Settings</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
var el=false; var el=false;
var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]; var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
function S() { function S() {
getLoc(); getLoc();
loadJS(getURL('/settings/s.js?p=5'), false, ()=>{BTa();}, ()=>{ loadJS(getURL('/settings/s.js?p=5'), false, ()=>{BTa();}, ()=>{
@ -119,9 +128,8 @@
if (parseFloat(d.Sf.LN.value)<0) { d.Sf.LNR.value = "W"; d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); } else d.Sf.LNR.value = "E"; if (parseFloat(d.Sf.LN.value)<0) { d.Sf.LNR.value = "W"; d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); } else d.Sf.LNR.value = "E";
} }
</script> </script>
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post" onsubmit="Wd()"> <form id="form_s" name="Sf" method="post" onsubmit="Wd()">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="H('features/settings/#time-settings')">?</button></div> <div class="helpB"><button type="button" onclick="H('features/settings/#time-settings')">?</button></div>

View File

@ -4,10 +4,19 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>UI Settings</title> <title>UI Settings</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
var initial_ds, initial_st, initial_su, oldUrl; var initial_ds, initial_st, initial_su, oldUrl;
var sett = null; var sett = null;
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
var l = { var l = {
"comp":{ "comp":{
"labels":"Show button labels", "labels":"Show button labels",
@ -208,9 +217,8 @@
gId("theme_bg_rnd").checked = false; gId("theme_bg_rnd").checked = false;
} }
</script> </script>
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post"> <form id="form_s" name="Sf" method="post">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="H('features/settings/#user-interface-settings')">?</button></div> <div class="helpB"><button type="button" onclick="H('features/settings/#user-interface-settings')">?</button></div>

View File

@ -4,12 +4,22 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>Usermod Settings</title> <title>Usermod Settings</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
var umCfg = {}; var umCfg = {};
var pins = [], pinO = [], owner; var pins = [], pinO = [], owner;
var urows; var urows;
var numM = 0; var numM = 0;
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
function S() { function S() {
getLoc(); getLoc();
// load settings and insert values into DOM // load settings and insert values into DOM
@ -269,10 +279,9 @@
if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914 if (d.Sf.checkValidity()) d.Sf.submit(); //https://stackoverflow.com/q/37323914
} }
</script> </script>
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post" onsubmit="svS(event)"> <form id="form_s" name="Sf" method="post" onsubmit="svS(event)">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="H()">?</button></div> <div class="helpB"><button type="button" onclick="H()">?</button></div>

View File

@ -4,8 +4,17 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<title>WiFi Settings</title> <title>WiFi Settings</title>
<script src="common.js" type="text/javascript"></script> <style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
<script> <script>
// load common.js with retry on error
(function loadFiles() {
const l = document.createElement('script');
l.src = 'common.js';
l.onload = () => loadResources(['style.css'], S); // load style.css then call S()
l.onerror = () => setTimeout(loadFiles, 100);
document.head.appendChild(l);
})();
var scanLoops = 0, preScanSSID = ""; var scanLoops = 0, preScanSSID = "";
var maxNetworks = 3; var maxNetworks = 3;
function N() { function N() {
@ -178,9 +187,8 @@ Static subnet mask:<br>
} }
</script> </script>
<style>@import url("style.css");</style>
</head> </head>
<body onload="S()"> <body>
<form id="form_s" name="Sf" method="post"> <form id="form_s" name="Sf" method="post">
<div class="toprow"> <div class="toprow">
<div class="helpB"><button type="button" onclick="H('features/settings/#wifi-settings')">?</button></div> <div class="helpB"><button type="button" onclick="H('features/settings/#wifi-settings')">?</button></div>

View File

@ -157,12 +157,6 @@ void sendDataWs(AsyncWebSocketClient * client)
// the following may no longer be necessary as heap management has been fixed by @willmmiles in AWS // the following may no longer be necessary as heap management has been fixed by @willmmiles in AWS
size_t heap1 = getFreeHeapSize(); size_t heap1 = getFreeHeapSize();
DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize()); DEBUG_PRINTF_P(PSTR("heap %u\n"), getFreeHeapSize());
#ifdef ESP8266
if (len>heap1) {
DEBUG_PRINTLN(F("Out of memory (WS)!"));
return;
}
#endif
AsyncWebSocketBuffer buffer(len); AsyncWebSocketBuffer buffer(len);
#ifdef ESP8266 #ifdef ESP8266
size_t heap2 = getFreeHeapSize(); size_t heap2 = getFreeHeapSize();