2022-05-08 10:50:48 +02:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "utf-8" >
2023-10-04 21:37:10 +02:00
< meta content = "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name = "viewport" >
2022-05-08 10:50:48 +02:00
< title > 2D Set-up< / title >
< script >
var d=document;
2023-06-04 18:40:29 +02:00
var loc = false, locip, locproto = "http:";
2023-01-02 20:56:00 +01:00
var maxPanels=64;
2023-02-12 13:18:30 +01:00
var ctx = null; // WLEDMM
2022-05-08 10:50:48 +02:00
function H(){window.open("https://kno.wled.ge/features/2D");}
2023-06-04 18:40:29 +02:00
function B(){window.open(getURL("/settings"),"_self");}
2022-05-08 10:50:48 +02:00
function gId(n){return d.getElementById(n);}
2023-03-05 22:56:14 +01:00
function fS(){d.Sf.submit();} // < button type = submit > sometimes didn't work
2022-06-23 17:42:02 +02:00
// https://www.educative.io/edpresso/how-to-dynamically-load-a-js-file-in-javascript
function loadJS(FILE_URL, async = true) {
let scE = d.createElement("script");
2023-11-09 05:45:48 +01:00
scE.setAttribute("src", FILE_URL + "& c=" + Date.now());
2022-06-23 17:42:02 +02:00
scE.setAttribute("type", "text/javascript");
scE.setAttribute("async", async);
d.body.appendChild(scE);
// success event
scE.addEventListener("load", () => {
//console.log("File loaded");
GetV();
UI();
2023-01-03 17:12:35 +01:00
Sf.MPC.setAttribute("max",maxPanels);
2022-06-23 17:42:02 +02:00
});
// error event
scE.addEventListener("error", (ev) => {
console.log("Error on loading file", ev);
alert("Loading of configuration script failed.\nIncomplete page data!");
});
}
function S() {
2023-06-07 21:37:54 +02:00
let l = window.location;
if (l.protocol == "file:") {
2023-01-16 22:12:02 +01:00
loc = true;
locip = localStorage.getItem('locIp');
if (!locip) {
locip = prompt("File Mode. Please enter WLED IP!");
localStorage.setItem('locIp', locip);
}
2023-06-04 18:40:29 +02:00
} else {
// detect reverse proxy
2023-06-07 21:37:54 +02:00
let path = l.pathname;
2023-06-04 18:40:29 +02:00
let paths = path.slice(1,path.endsWith('/')?-1:undefined).split("/");
if (paths.length > 2) {
2023-06-07 21:37:54 +02:00
locproto = l.protocol;
2023-06-04 18:40:29 +02:00
loc = true;
2023-06-08 07:14:03 +02:00
locip = l.hostname + (l.port ? ":" + l.port : "") + "/" + paths[0];
2023-06-04 18:40:29 +02:00
}
2023-01-16 22:12:02 +01:00
}
2023-06-04 18:40:29 +02:00
loadJS(getURL('/settings/s.js?p=10'), false); // If we set async false, file is loaded and executed, then next statement is processed
if (loc) d.Sf.action = getURL('/settings/2D');
}
function getURL(path) {
return (loc ? locproto + "//" + locip : "") + path;
2022-06-23 17:42:02 +02:00
}
2022-05-08 10:50:48 +02:00
2023-01-03 17:12:35 +01:00
function UI() {
2022-05-08 10:50:48 +02:00
if (gId("somp").value === "0") {
gId("mpdiv").style.display = "none";
resetPanels();
return;
}
2023-01-03 17:12:35 +01:00
gId("mpdiv").style.display = "block";
2023-02-12 13:18:30 +01:00
draw();
2022-05-08 10:50:48 +02:00
}
2023-02-10 19:49:43 +01:00
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(name) {
var req = new XMLHttpRequest();
req.addEventListener('load', function(){showToast(this.responseText,this.status >= 400)});
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;
}
2022-05-08 10:50:48 +02:00
function addPanels() {
2023-01-02 20:56:00 +01:00
let c = parseInt(d.Sf.MPC.value);
2023-01-03 17:12:35 +01:00
let i = gId("panels").children.length;
if (i< c ) for ( let j = i; j < c ; j + + ) addPanel ( j ) ;
if (i>c) for (let j=i; j>c; j--) remPanel();
2022-05-08 10:50:48 +02:00
}
function addPanel(i=0) {
let p = gId("panels");
if (p.children.length >= maxPanels) return;
2023-01-03 17:12:35 +01:00
var pw = parseInt(d.Sf.PW.value);
var ph = parseInt(d.Sf.PH.value);
2023-01-02 20:56:00 +01:00
let b = `< div id = "pnl${i}" > < hr class = "sml" > Panel ${i}< br >
2023-02-12 13:18:30 +01:00
1< sup > st< / sup > LED: < select name = "P${i}B" oninput = "UI()" >
2022-05-08 10:50:48 +02:00
< option value = "0" > Top< / option >
< option value = "1" > Bottom< / option >
2023-02-12 13:18:30 +01:00
< / select > < select name = "P${i}R" oninput = "UI()" >
2022-05-08 10:50:48 +02:00
< option value = "0" > Left< / option >
< option value = "1" > Right< / option >
< / select > < br >
2023-02-12 13:18:30 +01:00
Orientation: < select name = "P${i}V" oninput = "UI()" >
2022-05-08 10:50:48 +02:00
< option value = "0" > Horizontal< / option >
< option value = "1" > Vertical< / option >
< / select > < br >
2023-02-12 13:18:30 +01:00
Serpentine: < input type = "checkbox" name = "P${i}S" oninput = "UI()" > < br >
Dimensions (WxH): < input name = "P${i}W" type = "number" min = "1" max = "255" value = "${pw}" oninput = "UI()" > x < input name = "P${i}H" type = "number" min = "1" max = "255" value = "${ph}" oninput = "UI()" > < br >
Offset X:< input name = "P${i}X" type = "number" min = "0" max = "255" value = "0" oninput = "UI()" >
Y:< input name = "P${i}Y" type = "number" min = "0" max = "255" value = "0" oninput = "UI()" > < br > < i > (offset from top-left corner in # LEDs)< / i >
2023-01-02 20:56:00 +01:00
< / div > `;
2022-05-08 10:50:48 +02:00
p.insertAdjacentHTML("beforeend", b);
}
2023-01-16 22:12:02 +01:00
function remPanel() {
2022-05-08 10:50:48 +02:00
let p = gId("panels").children;
2023-01-16 22:12:02 +01:00
var i = p.length;
if (i < = 1) return;
p[i-1].remove();
}
2022-05-08 10:50:48 +02:00
function resetPanels() {
2023-01-02 20:56:00 +01:00
d.Sf.MPC.value = 1;
2023-01-03 17:12:35 +01:00
let e = gId("panels").children
for (let i = e.length; i>0; i--) e[i-1].remove();
2022-05-08 10:50:48 +02:00
}
2023-01-03 17:12:35 +01:00
/*
2022-05-08 10:50:48 +02:00
function btnPanel(i) {
gId("pnl_add").style.display = (i< maxPanels ) ? " inline " : " none " ;
gId("pnl_rem").style.display = (i>1) ? "inline":"none";
}
2023-01-03 17:12:35 +01:00
*/
2023-01-02 20:56:00 +01:00
function gen() {
resetPanels();
2023-02-10 19:49:43 +01:00
var pansH = parseInt(Sf.MPH.value);
var pansV = parseInt(Sf.MPV.value);
2023-01-02 20:56:00 +01:00
var c = pansH*pansV;
2023-02-10 19:49:43 +01:00
Sf.MPC.value = c; // number of panels
2023-01-02 20:56:00 +01:00
2023-02-10 19:49:43 +01:00
var ps = Sf.PS.checked;
var pv = Sf.PV.value==="1";
var pb = Sf.PB.value==="1";
var pr = Sf.PR.value==="1";
var pw = parseInt(Sf.PW.value);
var ph = parseInt(Sf.PH.value);
2023-01-02 20:56:00 +01:00
var h = pv ? pansV : pansH;
var v = pv ? pansH : pansV;
for (let j = 0, p = 0; j < v ; j + + ) {
for (let i = 0; i < h ; i + + , p + + ) {
if (j*i < maxPanels ) addPanel ( p ) ;
var y = (pv?pr:pb) ? v-j-1: j;
var x = (pv?pb:pr) ? h-i-1 : i;
x = ps & & j%2 ? h-x-1 : x;
2023-02-10 19:49:43 +01:00
Sf[`P${p}X`].value = (pv?y:x) * pw;
Sf[`P${p}Y`].value = (pv?x:y) * ph
Sf[`P${p}W`].value = pw;
Sf[`P${p}H`].value = ph;
2023-01-02 20:56:00 +01:00
}
}
}
2023-02-10 19:49:43 +01:00
function expand(o,i)
{
i.style.display = i.style.display!=="none" ? "none" : "";
2023-02-12 13:18:30 +01:00
o.style.rotate = i.style.display==="none" ? "none" : "90deg";
}
function draw() {
if (!ctx) {
//WLEDMM: add canvas, initialize and set UI
var canvas = gId("canvas");
canvas.width = window.innerWidth > 640?640:400; //Mobile gets 400, pc 640
canvas.height = canvas.width;
ctx = canvas.getContext('2d');
// window.requestAnimationFrame(animate);
}
//calc max height and width
var maxWidth = 0;
var maxHeight = 0;
for (let p=0; p< gId ( " panels " ) . children . length ; p + + ) {
var px = parseInt(Sf[`P${p}X`].value); //first led x
var py = parseInt(Sf[`P${p}Y`].value); //first led y
var pw = parseInt(Sf[`P${p}W`].value); //width
var ph = parseInt(Sf[`P${p}H`].value); //height
maxWidth = Math.max(maxWidth, px + pw);
maxHeight = Math.max(maxHeight, py + ph);
}
ctx.canvas.height = ctx.canvas.width / maxWidth * maxHeight;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
var space=0; // space between panels + margin
var ppL = (ctx.canvas.width - space * 2) / maxWidth; //pixels per led
ctx.lineWidth = 1;
ctx.strokeStyle="yellow";
ctx.strokeRect(0, 0, ctx.canvas.width, ctx.canvas.height); // add space between panels
for (let p=0; p< gId ( " panels " ) . children . length ; p + + ) {
var px = parseInt(Sf[`P${p}X`].value); //first led x
var py = parseInt(Sf[`P${p}Y`].value); //first led y
var pw = parseInt(Sf[`P${p}W`].value); //width
var ph = parseInt(Sf[`P${p}H`].value); //height
var pb = Sf[`P${p}B`].value == "1"; //bottom
var pr = Sf[`P${p}R`].value == "1"; //right
var pv = Sf[`P${p}V`].value == "1"; //vertical
var ps = Sf[`P${p}S`].checked; //serpentine
var topLeftX = px*ppL + space; //left margin
var topLeftY = py*ppL + space; //top margin
ctx.lineWidth = 3;
ctx.strokeStyle="white";
ctx.strokeRect(topLeftX, topLeftY, pw*ppL, ph*ppL); // add space between panels
var lnX;
var lnY;
//find start led
if (pb) //bottom
lnY = topLeftY + ph*ppL - ppL/2;
else //top
lnY = topLeftY + ppL/2;
if (pr) //right
lnX = topLeftX + pw*ppL - ppL/2;
else //left
lnX = topLeftX + ppL/2;
//first led
ctx.fillStyle = "green";
ctx.beginPath();
ctx.arc(lnX, lnY, ppL*0.5, 0, 2 * Math.PI);
ctx.fill();
//start line
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(lnX, lnY);
var longLineLength = (pv?ph:pw)*ppL - ppL;
for (let ln=0; ln< (pv?pw:ph); ln++) { //loop over panelwidth (or height of vertical?)
var serpLine = ps & & ln%2!=0; //serp: turn around if even line
if (pv) //if vertical
lnY += (pb?-1:1) * longLineLength * (serpLine?-1:1); //if vertical change the Y
else
lnX += (pr?-1:1) * longLineLength * (serpLine?-1:1); //if horizontal change the X
ctx.lineTo(lnX, lnY); //draw the long line
if (ln< (pv?pw:ph)-1) { //not the last
//find the small line end point
if (pv) //vertical
lnX += (pr?-1:1) * ppL;
else //horizontal
lnY += (pb?-1:1) * ppL;
//if serpentine go next else go down
if (ps) { //serpentine
ctx.lineTo(lnX, lnY); //draw the serpentine line
} else {
//find the other end of the long line
if (pv) //vertical
lnY += (pb?1:-1) * longLineLength * (serpLine?-1:1); //min as we go back
else //horizontal
lnX += (pr?1:-1) * longLineLength * (serpLine?-1:1);
ctx.moveTo(lnX, lnY); //move to the start point of the next long line
}
}
}
ctx.stroke();
//last led
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(lnX, lnY, ppL*0.5, 0, 2 * Math.PI);
ctx.fill();
ctx.font = '40px Arial';
ctx.fillStyle = "orange";
ctx.fillText(p, topLeftX + pw/2*ppL - 10, topLeftY + ph/2*ppL + 10);
}
gId("MD").innerHTML = "Matrix Dimensions (W*H=LC): " + maxWidth + " x " + maxHeight + " = " + maxWidth * maxHeight;
2023-02-10 19:49:43 +01:00
}
2022-05-08 10:50:48 +02:00
< / script >
< style > @ import url ( "style.css" ) ; < / style >
< / head >
< body onload = "S()" >
< form id = "form_s" name = "Sf" method = "post" >
< div class = "toprow" >
< div class = "helpB" > < button type = "button" onclick = "H()" > ?< / button > < / div >
2023-03-05 22:56:14 +01:00
< button type = "button" onclick = "B()" > Back< / button > < button type = "button" onclick = "fS()" > Save< / button > < hr >
2022-05-08 10:50:48 +02:00
< / div >
< h2 > 2D setup< / h2 >
2023-01-16 22:12:02 +01:00
Strip or panel:
2022-05-08 10:50:48 +02:00
< select id = "somp" name = "SOMP" onchange = "resetPanels();addPanels();UI();" >
< option value = "0" > 1D Strip< / option >
< option value = "1" > 2D Matrix< / option >
< / select > < br >
< div id = "mpdiv" style = "display:none;" >
2023-01-02 20:56:00 +01:00
< hr class = "sml" >
2023-02-10 19:49:43 +01:00
< h3 > Matrix Generator < button type = "button" id = "expGen" onclick = "expand(this,gId('mxGen'));" > > < / button > < / h3 >
< div id = "mxGen" style = "display:none;" >
Panel dimensions (WxH): < input name = "PW" type = "number" min = "1" max = "128" value = "8" > x < input name = "PH" type = "number" min = "1" max = "128" value = "8" > < br >
Horizontal panels: < input name = "MPH" type = "number" min = "1" max = "8" value = "1" >
Vertical panels: < input name = "MPV" type = "number" min = "1" max = "8" value = "1" > < br >
1< sup > st< / sup > panel: < select name = "PB" >
< option value = "0" > Top< / option >
< option value = "1" > Bottom< / option >
< / select > < select name = "PR" >
< option value = "0" > Left< / option >
< option value = "1" > Right< / option >
< / select > < br >
Orientation: < select name = "PV" >
< option value = "0" > Horizontal< / option >
< option value = "1" > Vertical< / option >
< / select > < br >
Serpentine: < input type = "checkbox" name = "PS" > < br >
2023-05-30 19:36:14 +02:00
< i class = "warn" > Pressing Populate will create LED panel layout with pre-arranged matrix.< br > Values above < i > will not< / i > affect final layout.< br >
2023-02-12 13:18:30 +01:00
WARNING: You may need to update each panel parameters after they are generated.< / i > < br >
2023-02-10 19:49:43 +01:00
< button type = "button" onclick = "gen();expand(gId('expGen'),gId('mxGen'));" > Populate< / button >
< / div >
2022-11-11 20:20:11 +01:00
< hr class = "sml" >
2023-01-02 20:56:00 +01:00
< h3 > Panel set-up< / h3 >
2023-02-10 19:49:43 +01:00
Number of panels: < input name = "MPC" type = "number" min = "1" max = "64" value = "1" oninput = "addPanels();UI();" > < br >
2023-01-02 20:56:00 +01:00
< i > A matrix is made of 1 or more physical LED panels.< br >
Each panel can be of different size and/or have different LED orientation and/or starting point and/or layout.< / i > < br >
2022-08-09 21:14:37 +02:00
< h3 > LED panel layout< / h3 >
2022-05-08 10:50:48 +02:00
< div id = "panels" >
< / div >
2023-02-10 19:49:43 +01:00
< hr class = "sml" >
2023-02-12 13:18:30 +01:00
< div id = "MD" > < / div >
< canvas id = "canvas" > < / canvas >
2023-02-10 19:49:43 +01:00
< div id = "json" > Gap file: < input type = "file" name = "data" accept = ".json" > < button type = "button" class = "sml" onclick = "uploadFile('/2d-gaps.json')" > Upload< / button > < / div >
< i > Note: Gap file is a < b > .json< / b > file containing an array with number of elements equal to the matrix size.< br >
A value of -1 means that pixel at that position is missing, a value of 0 means never paint that pixel, and 1 means regular pixel.< / i >
2022-05-08 10:50:48 +02:00
< / div >
< hr >
2023-03-05 22:56:14 +01:00
< button type = "button" onclick = "B()" > Back< / button > < button type = "button" onclick = "fS()" > Save< / button >
2022-05-08 10:50:48 +02:00
< / form >
2023-02-10 19:49:43 +01:00
< div id = "toast" > < / div >
2022-05-08 10:50:48 +02:00
< / body >
< / html >