web
This commit is contained in:
@@ -4,426 +4,116 @@ const char index_html[] PROGMEM = R"rawliteral(
|
||||
<title>LED Panel Control</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body { font-family: Arial; text-align: center; margin:0px auto; padding-top: 30px;}
|
||||
.button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 20px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
margin: 4px 2px;
|
||||
cursor: pointer;
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: Arial, sans-serif; background: #1a1a2e; color: #eee; padding: 20px; }
|
||||
h1 { text-align: center; margin-bottom: 30px; font-size: 1.5em; }
|
||||
.container { max-width: 400px; margin: 0 auto; }
|
||||
.card {
|
||||
background: #16213e;
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
textarea {
|
||||
width: 80%;
|
||||
height: 200px;
|
||||
margin-top: 20px;
|
||||
.card-label { font-size: 1em; }
|
||||
.card-label small { display: block; color: #888; font-size: 0.75em; margin-top: 2px; }
|
||||
.toggle { position: relative; width: 52px; height: 28px; flex-shrink: 0; }
|
||||
.toggle input { opacity: 0; width: 0; height: 0; }
|
||||
.toggle .slider {
|
||||
position: absolute; cursor: pointer;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: #444; border-radius: 28px;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.text-controls {
|
||||
margin-top: 20px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
.toggle .slider:before {
|
||||
content: ""; position: absolute;
|
||||
height: 22px; width: 22px;
|
||||
left: 3px; bottom: 3px;
|
||||
background: #fff; border-radius: 50%;
|
||||
transition: 0.3s;
|
||||
}
|
||||
.text-input-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.param-explanation {
|
||||
font-size: 0.8em;
|
||||
color: #666;
|
||||
}
|
||||
pre {
|
||||
text-align: left;
|
||||
background-color: #f0f0f0;
|
||||
padding: 10px;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
#nodes-container {
|
||||
margin-top: 20px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.node-item {
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.modal {
|
||||
display: none; /* Hidden by default */
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 1; /* Sit on top */
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%; /* Full width */
|
||||
height: 100%; /* Full height */
|
||||
overflow: auto; /* Enable scroll if needed */
|
||||
background-color: rgb(0,0,0); /* Fallback color */
|
||||
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
|
||||
}
|
||||
.modal-content {
|
||||
background-color: #fefefe;
|
||||
margin: 15% auto; /* 15% from the top and centered */
|
||||
padding: 20px;
|
||||
border: 1px solid #888;
|
||||
width: 80%; /* Could be more or less, depending on screen size */
|
||||
}
|
||||
.close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: black;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
.toggle input:checked + .slider { background: #4CAF50; }
|
||||
.toggle input:checked + .slider:before { transform: translateX(24px); }
|
||||
.brightness-card {
|
||||
background: #16213e;
|
||||
border-radius: 12px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.brightness-card label { font-size: 1em; display: block; margin-bottom: 8px; }
|
||||
.brightness-row { display: flex; align-items: center; gap: 12px; }
|
||||
.brightness-row input[type=range] { flex: 1; accent-color: #4CAF50; }
|
||||
.brightness-row span { min-width: 32px; text-align: right; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>LED Panel Control</h1>
|
||||
<button class="button" onclick="location.href='/upload-page'">Upload Image</button>
|
||||
<br>
|
||||
<div>
|
||||
<label for="brightness">Brightness: </label>
|
||||
<input type="range" id="brightness" min="0" max="100" value="100" onchange="updateBrightness()">
|
||||
<span id="brightnessValue">100</span>
|
||||
</div>
|
||||
<br>
|
||||
<div class="container">
|
||||
<h1>LED Panel Control</h1>
|
||||
|
||||
<div id="nodes-container">
|
||||
<h2>Current Text Nodes</h2>
|
||||
<div id="nodes-list"></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-label">Top Text<small>Witamy w PTI</small></div>
|
||||
<label class="toggle"><input type="checkbox" id="top_text" checked onchange="toggle('top_text', this.checked)"><span class="slider"></span></label>
|
||||
</div>
|
||||
|
||||
<div id="modify-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" onclick="closeModal()">×</span>
|
||||
<h3>Modify Text Node</h3>
|
||||
<div class="text-input-group">
|
||||
<input type="hidden" id="modifyGlobalIdInput">
|
||||
<b>Node Global ID:</b> <span id="modifyGlobalIdDisplay"></span>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="text" id="modifyTextInput" placeholder="New text...">
|
||||
<div class="param-explanation">New text for the node.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="color" id="modifyColorPicker">
|
||||
<div class="param-explanation">New color for the text.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="number" id="modifySlownessInput" placeholder="Slowness" min="0" max="255">
|
||||
<div class="param-explanation">Animation slowness (0-255).</div>
|
||||
</div>
|
||||
<div class="text-buttons">
|
||||
<button class="button" onclick="modifyText()">Save Changes</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-label">Bottom Text<small>School programs</small></div>
|
||||
<label class="toggle"><input type="checkbox" id="bottom_text" checked onchange="toggle('bottom_text', this.checked)"><span class="slider"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modify-multicolor-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close" onclick="closeMultiColorModal()">×</span>
|
||||
<h3>Modify Multi-Color Text Node</h3>
|
||||
<div class="text-input-group">
|
||||
<input type="hidden" id="modifyMultiColorGlobalIdInput">
|
||||
<b>Node Global ID:</b> <span id="modifyMultiColorGlobalIdDisplay"></span>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="text" id="modifyMultiColorTextInput" placeholder="New text...">
|
||||
<div class="param-explanation">New text for the node.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="text" id="modifyMultiColorColorInput" placeholder="e.g., #ff0000,#00ff00,#0000ff">
|
||||
<div class="param-explanation">Comma-separated list of hex colors (max 4).</div>
|
||||
</div>
|
||||
<div class="text-buttons">
|
||||
<button class="button" onclick="modifyMultiColorText()">Save Changes</button>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-label">Vehicles<small>Vehicle interruptions</small></div>
|
||||
<label class="toggle"><input type="checkbox" id="vehicles" checked onchange="toggle('vehicles', this.checked)"><span class="slider"></span></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-controls">
|
||||
<h3>Add Text Node (Full Control)</h3>
|
||||
<pre><code>void addNewTextNode(char text[TEXT_MAX_LENGTH + 1], uint32_t color, bool handle_pos_via_cursor = true, short pos_x = 0, short pos_y = 0, unsigned char scroll_slowness = 1, bool is_scrolling = true, bool is_small = true, short disappear_time = -1)</code></pre>
|
||||
<div class="text-input-group">
|
||||
<input type="text" id="textInput" placeholder="Enter text...">
|
||||
<div class="param-explanation">The text to display.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="color" id="textColorPicker" value="#ffffff">
|
||||
<div class="param-explanation">Color of the text.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="number" id="xInput" placeholder="X position">
|
||||
<div class="param-explanation">X coordinate of the text.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="number" id="yInput" placeholder="Y position">
|
||||
<div class="param-explanation">Y coordinate of the text.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<select id="fontSizeInput">
|
||||
<option value="small">Small</option>
|
||||
<option value="medium">Medium</option>
|
||||
</select>
|
||||
<div class="param-explanation">Font size of the text.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="number" id="slownessInput" placeholder="Slowness" value="2" min="0" max="255">
|
||||
<div class="param-explanation">Animation slowness (0-255).</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="checkbox" id="isRepeatingInput">
|
||||
<label for="isRepeatingInput">Repeating</label>
|
||||
<div class="param-explanation">If checked, the text will wrap around the screen.</div>
|
||||
</div>
|
||||
<div class="text-buttons">
|
||||
<button class="button" onclick="sendText()">Add Text</button>
|
||||
<button class="button" onclick="sendText('top')">Send to Top</button>
|
||||
<button class="button" onclick="sendText('bottom')">Send to Bottom</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-controls">
|
||||
<h3>Add Multicolor Text Node</h3>
|
||||
<pre><code>void addNewMultiColor(char text[TEXT_MAX_LENGTH + 1], RGBWithIndex colors[4], unsigned char color_count, bool handle_pos_via_cursor = true, short pos_x = 0, short pos_y = 0, unsigned char scroll_slowness = 1, bool is_scrolling = true, bool is_small = true, short disappear_time = -1)</code></pre>
|
||||
<div class="text-input-group">
|
||||
<input type="text" id="multicolorTextInput" placeholder="Enter text...">
|
||||
<div class="param-explanation">The text to display.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="text" id="multicolorColorInput" placeholder="e.g., #ff0000,#00ff00,#0000ff">
|
||||
<div class="param-explanation">Comma-separated list of hex colors (max 4).</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="number" id="multicolorXInput" placeholder="X position">
|
||||
<div class="param-explanation">X coordinate of the text.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="number" id="multicolorYInput" placeholder="Y position">
|
||||
<div class="param-explanation">Y coordinate of the text.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<select id="multicolorFontSizeInput">
|
||||
<option value="small">Small</option>
|
||||
<option value="medium">Medium</option>
|
||||
</select>
|
||||
<div class="param-explanation">Font size of the text.</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="number" id="multicolorSlownessInput" placeholder="Slowness" value="2" min="0" max="255">
|
||||
<div class="param-explanation">Animation slowness (0-255).</div>
|
||||
</div>
|
||||
<div class="text-input-group">
|
||||
<input type="checkbox" id="multicolorIsRepeatingInput">
|
||||
<label for="multicolorIsRepeatingInput">Repeating</label>
|
||||
<div class="param-explanation">If checked, the text will wrap around the screen.</div>
|
||||
</div>
|
||||
<div class="text-buttons">
|
||||
<button class="button" onclick="sendMulticolorText()">Add Multicolor Text</button>
|
||||
<div class="brightness-card">
|
||||
<label>Brightness</label>
|
||||
<div class="brightness-row">
|
||||
<input type="range" id="brightness" min="0" max="100" value="100" oninput="updateBrightnessLabel()" onchange="sendBrightness()">
|
||||
<span id="brightnessValue">100</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function updateBrightness() {
|
||||
var brightness = document.getElementById("brightness").value;
|
||||
document.getElementById("brightnessValue").innerText = brightness;
|
||||
function toggle(program, enabled) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/toggle-program", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.send("program=" + program + "&enabled=" + (enabled ? "true" : "false"));
|
||||
}
|
||||
|
||||
function updateBrightnessLabel() {
|
||||
document.getElementById("brightnessValue").innerText = document.getElementById("brightness").value;
|
||||
}
|
||||
|
||||
function sendBrightness() {
|
||||
var val = document.getElementById("brightness").value;
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/brightness", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
xhr.send("value=" + brightness);
|
||||
xhr.send("value=" + val);
|
||||
}
|
||||
|
||||
function sendText(position) {
|
||||
const text = document.getElementById('textInput').value;
|
||||
const color = document.getElementById('textColorPicker').value;
|
||||
const slowness = document.getElementById('slownessInput').value;
|
||||
const x = document.getElementById('xInput').value;
|
||||
const y = document.getElementById('yInput').value;
|
||||
const fontSize = document.getElementById('fontSizeInput').value;
|
||||
const isRepeating = document.getElementById('isRepeatingInput').checked;
|
||||
|
||||
if (!text) {
|
||||
alert('Please enter some text.');
|
||||
return;
|
||||
}
|
||||
|
||||
function fetchStatus() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/text", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
let data = "text=" + encodeURIComponent(text) + "&color=" + encodeURIComponent(color) + "&slowness=" + slowness + "&is_repeating=" + isRepeating;
|
||||
if(position) {
|
||||
data += "&position=" + position;
|
||||
} else {
|
||||
if(x) data += "&x=" + x;
|
||||
if(y) data += "&y=" + y;
|
||||
}
|
||||
if(fontSize) data += "&fontSize=" + fontSize;
|
||||
|
||||
xhr.open("GET", "/program-status", true);
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
fetchNodes();
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
}
|
||||
|
||||
function sendMulticolorText() {
|
||||
const text = document.getElementById('multicolorTextInput').value;
|
||||
const colors = document.getElementById('multicolorColorInput').value;
|
||||
const slowness = document.getElementById('multicolorSlownessInput').value;
|
||||
const x = document.getElementById('multicolorXInput').value;
|
||||
const y = document.getElementById('multicolorYInput').value;
|
||||
const fontSize = document.getElementById('multicolorFontSizeInput').value;
|
||||
const isRepeating = document.getElementById('multicolorIsRepeatingInput').checked;
|
||||
|
||||
if (!text) {
|
||||
alert('Please enter some text.');
|
||||
return;
|
||||
}
|
||||
if (!colors) {
|
||||
alert('Please enter some colors.');
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/multicolor-text", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
let data = "text=" + encodeURIComponent(text) + "&colors=" + encodeURIComponent(colors) + "&slowness=" + slowness + "&is_repeating=" + isRepeating;
|
||||
if(x) data += "&x=" + x;
|
||||
if(y) data += "&y=" + y;
|
||||
if(fontSize) data += "&fontSize=" + fontSize;
|
||||
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
fetchNodes();
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
}
|
||||
|
||||
function modifyText() {
|
||||
const global_id = document.getElementById('modifyGlobalIdInput').value;
|
||||
const color = document.getElementById('modifyColorPicker').value;
|
||||
const text = document.getElementById('modifyTextInput').value;
|
||||
const slowness = document.getElementById('modifySlownessInput').value;
|
||||
|
||||
if (!global_id) {
|
||||
alert('Please enter a node ID.');
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/modify-text", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
let data = "global_id=" + global_id + "&color=" + encodeURIComponent(color) + "&text=" + encodeURIComponent(text) + "&slowness=" + slowness;
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
fetchNodes();
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
}
|
||||
|
||||
function fetchNodes() {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", "/nodes", true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (this.readyState == 4 && this.status == 200) {
|
||||
var nodes = JSON.parse(this.responseText);
|
||||
var nodesList = document.getElementById('nodes-list');
|
||||
nodesList.innerHTML = '';
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
var nodeDiv = document.createElement('div');
|
||||
nodeDiv.className = 'node-item';
|
||||
if (node.type === 'text') {
|
||||
var color = '#' + ('000000' + node.color.toString(16)).slice(-6);
|
||||
nodeDiv.innerHTML = '<b>ID:</b> ' + node.global_id + ', <b>Text:</b> ' + node.text + ', <b>Color:</b> <span style="color:' + color + '">' + color + '</span>' +
|
||||
'<button style="margin-left: 10px;" onclick="populateModifyForm(' + node.global_id + ', \'' + color + '\', \'' + node.text + '\', ' + node.slowness + ')">Modify</button>';
|
||||
} else if (node.type === 'multi-color') {
|
||||
var colorsStr = node.colors.map(c => '#' + ('000000' + ((c.r << 16) | (c.g << 8) | c.b).toString(16)).slice(-6)).join(',');
|
||||
nodeDiv.innerHTML = '<b>ID:</b> ' + node.global_id + ', <b>Text:</b> ' + node.text + ', <b>Type:</b> Multi-Color' +
|
||||
'<button style="margin-left: 10px;" onclick="populateMultiColorModifyForm(' + node.global_id + ', \'' + colorsStr + '\', \'' + node.text + '\')">Modify</button>';
|
||||
}
|
||||
nodesList.appendChild(nodeDiv);
|
||||
}
|
||||
var s = JSON.parse(xhr.responseText);
|
||||
document.getElementById("top_text").checked = s.top_text;
|
||||
document.getElementById("bottom_text").checked = s.bottom_text;
|
||||
document.getElementById("vehicles").checked = s.vehicles;
|
||||
document.getElementById("brightness").value = s.brightness;
|
||||
document.getElementById("brightnessValue").innerText = s.brightness;
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function populateModifyForm(global_id, color, text, slowness) {
|
||||
document.getElementById('modifyGlobalIdInput').value = global_id;
|
||||
document.getElementById('modifyGlobalIdDisplay').innerText = global_id;
|
||||
document.getElementById('modifyColorPicker').value = color;
|
||||
document.getElementById('modifyTextInput').value = text;
|
||||
document.getElementById('modifySlownessInput').value = slowness;
|
||||
document.getElementById('modify-modal').style.display = "block";
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('modify-modal').style.display = "none";
|
||||
}
|
||||
|
||||
function populateMultiColorModifyForm(global_id, colors, text) {
|
||||
document.getElementById('modifyMultiColorGlobalIdInput').value = global_id;
|
||||
document.getElementById('modifyMultiColorGlobalIdDisplay').innerText = global_id;
|
||||
document.getElementById('modifyMultiColorColorInput').value = colors;
|
||||
document.getElementById('modifyMultiColorTextInput').value = text;
|
||||
document.getElementById('modify-multicolor-modal').style.display = "block";
|
||||
}
|
||||
|
||||
function closeMultiColorModal() {
|
||||
document.getElementById('modify-multicolor-modal').style.display = "none";
|
||||
}
|
||||
|
||||
function modifyMultiColorText() {
|
||||
const global_id = document.getElementById('modifyMultiColorGlobalIdInput').value;
|
||||
const colors = document.getElementById('modifyMultiColorColorInput').value;
|
||||
const text = document.getElementById('modifyMultiColorTextInput').value;
|
||||
|
||||
if (!global_id) {
|
||||
alert('Please enter a node ID.');
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", "/modify-multicolor-text", true);
|
||||
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
|
||||
let data = "global_id=" + global_id + "&colors=" + encodeURIComponent(colors) + "&text=" + encodeURIComponent(text);
|
||||
xhr.onload = function() {
|
||||
if (xhr.status === 200) {
|
||||
fetchNodes();
|
||||
closeMultiColorModal();
|
||||
}
|
||||
};
|
||||
xhr.send(data);
|
||||
}
|
||||
|
||||
window.onclick = function(event) {
|
||||
if (event.target == document.getElementById('modify-modal')) {
|
||||
closeModal();
|
||||
}
|
||||
if (event.target == document.getElementById('modify-multicolor-modal')) {
|
||||
closeMultiColorModal();
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(fetchNodes, 5000);
|
||||
window.onload = fetchNodes;
|
||||
window.onload = fetchStatus;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
)rawliteral";
|
||||
)rawliteral";
|
||||
|
||||
Reference in New Issue
Block a user