425 lines
15 KiB
HTML
425 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=500">
|
|
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport"/>
|
|
<title>2D Set-up</title>
|
|
<script>
|
|
var d=document;
|
|
var loc = false, locip;
|
|
var maxPanels=64;
|
|
var ctx = null; // WLEDMM
|
|
var ledIndex = 0; // WLEDMM
|
|
var wasAdvanced = -1; //WLEDMM
|
|
function H(){window.open("https://mm.kno.wled.ge/features/2D");}
|
|
function B(){window.open("/settings","_self");}
|
|
function gId(n){return d.getElementById(n);}
|
|
// 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");
|
|
scE.setAttribute("src", FILE_URL);
|
|
scE.setAttribute("type", "text/javascript");
|
|
scE.setAttribute("async", async);
|
|
d.body.appendChild(scE);
|
|
// success event
|
|
scE.addEventListener("load", () => {
|
|
//console.log("File loaded");
|
|
GetV();
|
|
UI();
|
|
Sf.MPC.setAttribute("max",maxPanels);
|
|
baChange(); //WLEDMM: Set Basic or advanced blocks
|
|
fieldChange(); //WLEDMM: Show or hide 1st Panel
|
|
draw(); //WLEDMM: draw graphics
|
|
gId("LC").innerHTML = d.Sf.LC.value; //WLEDMM: add Total LEDs
|
|
});
|
|
// error event
|
|
scE.addEventListener("error", (ev) => {
|
|
console.log("Error on loading file", ev);
|
|
alert("Loading of configuration script failed.\nIncomplete page data!");
|
|
});
|
|
}
|
|
function S() {
|
|
if (window.location.protocol == "file:") {
|
|
loc = true;
|
|
locip = localStorage.getItem('locIp');
|
|
if (!locip) {
|
|
locip = prompt("File Mode. Please enter WLED IP!");
|
|
localStorage.setItem('locIp', locip);
|
|
}
|
|
}
|
|
var url = (loc?`http://${locip}`:'') + '/settings/s.js?p=10';
|
|
loadJS(url, false); // If we set async false, file is loaded and executed, then next statement is processed
|
|
}
|
|
|
|
function UI() {
|
|
if (gId("somp").value === "0") {
|
|
gId("mpdiv").style.display = "none";
|
|
resetPanels();
|
|
return;
|
|
}
|
|
gId("mpdiv").style.display = "block";
|
|
}
|
|
|
|
function addPanels() {
|
|
let c = parseInt(d.Sf.MPC.value);
|
|
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();
|
|
}
|
|
|
|
function addPanel(i=0) {
|
|
let p = gId("panels");
|
|
if (p.children.length >= maxPanels) return;
|
|
var pw = parseInt(d.Sf.PW.value);
|
|
var ph = parseInt(d.Sf.PH.value);
|
|
//WLEDMM: change name to id
|
|
let b = `<div id="pnl${i}"><hr class="sml">Panel ${i}<br>
|
|
1<sup>st</sup> LED: <select id="P${i}B" name="P${i}B" oninput="draw()">
|
|
<option value="0">Top</option>
|
|
<option value="1">Bottom</option>
|
|
</select><select id="P${i}R" name="P${i}R" oninput="draw()">
|
|
<option value="0">Left</option>
|
|
<option value="1">Right</option>
|
|
</select><br>
|
|
Orientation: <select id="P${i}V" name="P${i}V" oninput="draw()">
|
|
<option value="0">Horizontal</option>
|
|
<option value="1">Vertical</option>
|
|
</select><br>
|
|
Serpentine: <input id="P${i}S" name="P${i}S" type="checkbox" onclick="draw()"><br>
|
|
Dimensions (WxH): <input id="P${i}W" name="P${i}W" type="number" min="1" max="255" value="${pw}" oninput="draw()"> x <input id="P${i}H" name="P${i}H" type="number" min="1" max="255" value="${ph}" oninput="draw()"><br>
|
|
Offset X:<input id="P${i}X" name="P${i}X" type="number" min="0" max="255" value="0" oninput="draw()">
|
|
Y:<input id="P${i}Y" name="P${i}Y" type="number" min="0" max="255" value="0" oninput="draw()"><br><i>(offset from top-left corner in # LEDs)</i>
|
|
</div>`;
|
|
p.insertAdjacentHTML("beforeend", b);
|
|
}
|
|
|
|
function remPanel() {
|
|
let p = gId("panels").children;
|
|
var i = p.length;
|
|
if (i <= 1) return;
|
|
p[i-1].remove();
|
|
}
|
|
|
|
function resetPanels() {
|
|
d.Sf.MPC.value = 1;
|
|
let e = gId("panels").children
|
|
for (let i = e.length; i>0; i--) e[i-1].remove();
|
|
}
|
|
/*
|
|
function btnPanel(i) {
|
|
gId("pnl_add").style.display = (i<maxPanels) ? "inline":"none";
|
|
gId("pnl_rem").style.display = (i>1) ? "inline":"none";
|
|
}
|
|
*/
|
|
|
|
//WLEDMM
|
|
function baChange(radioElement=null) {
|
|
// console.log("baChange", gId("form_s"), radioElement, gId("form_s").elements, d.Sf.BA.value);
|
|
if (radioElement) {
|
|
if (d.Sf.BA.value == 0 && wasAdvanced == 1) {
|
|
if (!confirm('Are you sure to go back to basic (advanced settings will be lost)?')) {
|
|
d.Sf.BA.value = 1;
|
|
return;
|
|
}
|
|
fieldChange(); //Create basic layout
|
|
}
|
|
// console.log("baChange", gId("form_s"), radioElement, gId("form_s").elements, d.Sf.BA.value, radioElement.value);
|
|
d.Sf.BA.value = parseInt(radioElement.value);
|
|
}
|
|
|
|
wasAdvanced = d.Sf.BA.value == 1;
|
|
|
|
gId("advancedBlock").style.display = wasAdvanced ? "inline":"none";
|
|
gId("title").innerHTML = !wasAdvanced ? "Matrix Setup":"Matrix Generator";
|
|
}
|
|
|
|
//WLEDMM
|
|
function fieldChange(force=false) { //Done on all fields of matrix setup area
|
|
// console.log("fieldChange", force);
|
|
var pansH = parseInt(d.Sf.MPH.value);
|
|
var pansV = parseInt(d.Sf.MPV.value);
|
|
if ((pansH>0 && pansV>0 && d.Sf.BA.value == 0) || force) gen(); //Generate if basic or forced (in advanced by populate button)
|
|
draw();
|
|
gId("panelOrientationBlock").style.display = pansH*pansV>1?"inline":"none"; //WLEDMM: panel orientation only needed when more than one panel
|
|
gId("popButton").disabled = force;
|
|
gId("popButton").style.color= force?"grey":"red";
|
|
}
|
|
|
|
function gen() {
|
|
resetPanels();
|
|
|
|
var pansH = parseInt(d.Sf.MPH.value);
|
|
var pansV = parseInt(d.Sf.MPV.value);
|
|
var c = pansH*pansV;
|
|
d.Sf.MPC.value = c; // number of panels
|
|
|
|
var ps = d.Sf.PS.checked;
|
|
var pv = d.Sf.PV.value==="1";
|
|
var pb = d.Sf.PB.value==="1";
|
|
var pr = d.Sf.PR.value==="1";
|
|
var pw = parseInt(d.Sf.PW.value);
|
|
var ph = parseInt(d.Sf.PH.value);
|
|
|
|
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;
|
|
gId("P"+p+"X").value = (pv?y:x) * pw;
|
|
gId("P"+p+"Y").value = (pv?x:y) * ph;
|
|
gId("P"+p+"W").value = pw;
|
|
gId("P"+p+"H").value = ph;
|
|
//WLEDMM: also calculate orientation of panel !
|
|
gId("P"+p+"B").value = d.Sf.PBL.value;
|
|
gId("P"+p+"R").value = d.Sf.PRL.value;
|
|
gId("P"+p+"V").value = d.Sf.PVL.value;
|
|
gId("P"+p+"S").checked = d.Sf.PSL.checked;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
//WLEDMM
|
|
function draw() {
|
|
|
|
if (!ctx) {
|
|
//WLEDMM: add canvas, initialize and set UI
|
|
var canvas = gId("canvasPanels");
|
|
canvas.width = window.innerWidth > 800?800:400; //Mobile gets 400, pc 800
|
|
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(gId("P"+p+"X").value); //first led x
|
|
var py = parseInt(gId("P"+p+"Y").value); //first led y
|
|
var pw = parseInt(gId("P"+p+"W").value); //width
|
|
var ph = parseInt(gId("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
|
|
// console.log("dim", ctx.canvas.width , maxWidth, ctx.canvas.height , maxHeight, ppL);
|
|
|
|
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++) {
|
|
// console.log(gId("P"+p+"X").value, gId("P"+p+"Y").value, gId("P"+p+"W").value, gId("P"+p+"H").value, gId("P"+p+"B").value, gId("P"+p+"R").value, gId("P"+p+"V").value, gId("P"+p+"S").checked);
|
|
|
|
var px = parseInt(gId("P"+p+"X").value); //first led x
|
|
var py = parseInt(gId("P"+p+"Y").value); //first led y
|
|
var pw = parseInt(gId("P"+p+"W").value); //width
|
|
var ph = parseInt(gId("P"+p+"H").value); //height
|
|
|
|
var pb = gId("P"+p+"B").value == "1"; //bottom
|
|
var pr = gId("P"+p+"R").value == "1"; //right
|
|
var pv = gId("P"+p+"V").value == "1"; //vertical
|
|
var ps = gId("P"+p+"S").checked; //serpentine
|
|
|
|
// console.log("panel", p, px,py,pw,ph);
|
|
var topLeftX = px*ppL + space; //left margin
|
|
var topLeftY = py*ppL + space; //top margin
|
|
// console.log("rect", p, topLeftX, topLeftY, pw*ppL, ph*ppL);
|
|
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;
|
|
}
|
|
|
|
// //WLEDMM
|
|
// function getCoordOfLed(ledIndex) {
|
|
|
|
// var pw = parseInt(d.Sf.PW.value);
|
|
// var ph = parseInt(d.Sf.PH.value);
|
|
|
|
// //calculate which panel
|
|
// //panelsize
|
|
// var p = parseInt(ledIndex / (pw*ph));
|
|
// var ledPanelIndex = ledIndex % (pw*ph);
|
|
// // console.log(ledIndex, p, ledPanelIndex);
|
|
|
|
// //calculate which led in panel
|
|
|
|
// //calculate coordinates
|
|
|
|
// }
|
|
|
|
// //WLEDMM
|
|
// function animate() {
|
|
// // clear previous led
|
|
|
|
// var pw = parseInt(d.Sf.PW.value);
|
|
// var ph = parseInt(d.Sf.PH.value);
|
|
// var pansH = parseInt(d.Sf.MPH.value);
|
|
// var pansV = parseInt(d.Sf.MPV.value);
|
|
|
|
// getCoordOfLed(ledIndex);
|
|
// ledIndex = (ledIndex + 1) % (pw*pansH*ph*pansV);
|
|
|
|
// window.requestAnimationFrame(animate);
|
|
// }
|
|
</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>
|
|
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
|
|
</div>
|
|
<h2>2D setup</h2>
|
|
Strip or panel:
|
|
<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;">
|
|
<hr class="sml">
|
|
Interface:
|
|
<input type="radio" value="0" name="BA" onclick="baChange(this)"><label>Basic</label>
|
|
<input type="radio" value="1" name="BA" onclick="baChange(this)"><label>Advanced</label> ☾
|
|
<br>
|
|
<hr class="sml">
|
|
<h3 id="title">Matrix Generator</h3>
|
|
Panel dimensions (WxH): <input name="PW" type="number" min="1" max="255" value="8" oninput="fieldChange()"> x <input name="PH" type="number" min="1" max="255" value="8" oninput="fieldChange()"><br>
|
|
Horizontal panels: <input name="MPH" type="number" min="1" max="8" value="1" oninput="fieldChange()">
|
|
Vertical panels: <input name="MPV" type="number" min="1" max="8" value="1" oninput="fieldChange()"><br>
|
|
<div id="panelOrientationBlock">
|
|
1<sup>st</sup> panel: <select name="PB" oninput="fieldChange()">
|
|
<option value="0">Top</option>
|
|
<option value="1">Bottom</option>
|
|
</select><select name="PR" oninput="fieldChange()">
|
|
<option value="0">Left</option>
|
|
<option value="1">Right</option>
|
|
</select><br>
|
|
Orientation: <select name="PV" oninput="fieldChange()">
|
|
<option value="0">Horizontal</option>
|
|
<option value="1">Vertical</option>
|
|
</select><br>
|
|
Serpentine: <input type="checkbox" name="PS" onclick="fieldChange()"><br>
|
|
<br>
|
|
</div> <!--panelOrientationBlock-->
|
|
1<sup>st</sup> LED ☾: <select name="PBL" oninput="fieldChange()">
|
|
<option value="0">Top</option>
|
|
<option value="1">Bottom</option>
|
|
</select><select name="PRL" oninput="fieldChange()">
|
|
<option value="0">Left</option>
|
|
<option value="1">Right</option>
|
|
</select><br>
|
|
Orientation: <select name="PVL" oninput="fieldChange()">
|
|
<option value="0">Horizontal</option>
|
|
<option value="1">Vertical</option>
|
|
</select><br>
|
|
Serpentine: <input type="checkbox" name="PSL" onclick="fieldChange()"><br>
|
|
<br>
|
|
<hr class="sml">
|
|
☾<br>
|
|
<canvas id="canvasPanels"></canvas><br> <!--WLEDMM panel visualization-->
|
|
<div id="MD"></div> <!-- WLEDMM Matrix dimensions -->
|
|
(Total LEDs: <input name="LC" hidden><span id="LC"></span>)<br> <!--WLEDMM: input is placeholder for d.Sf.LC.value-->
|
|
<br>
|
|
<div id="advancedBlock">
|
|
<i style="color:#fa0;">Can populate LED panel layout with pre-arranged matrix.<br>These values do not affect final layout.<br>WLEDMM: Populate will overwrite earlier saved panel layouts!</i><br>
|
|
<button id="popButton" type="button" onclick="fieldChange(true)">Populate</button>
|
|
<hr class="sml">
|
|
<h3>Panel set-up</h3>
|
|
Number of panels: <input name="MPC" type="number" min="1" max="64" value="1" oninput="addPanels()"><br>
|
|
<i>A matrix is made of 1 or more physical LED panels.<br>
|
|
<!--Panels should be arranged from top-left to bottom-right order, starting with lower panel number on the left (or top if transposed).<br>-->
|
|
Each panel can be of different size and/or have different LED orientation and/or starting point and/or layout.</i><br>
|
|
<h3>LED panel layout</h3>
|
|
<div id="panels">
|
|
</div>
|
|
</div> <!--advancedBlock-->
|
|
</div>
|
|
<hr>
|
|
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
|
</form>
|
|
</body>
|
|
</html>
|