mirror of
https://github.com/wled/WLED.git
synced 2026-04-20 22:22:51 +00:00
* convert to vectors
* Update wled00/cfg.cpp
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* UI fixes
converted preset entry to select box for ease of use
* no eeprom compatibility
* tighten up code
* fix sunrise/sunset issue
* fix issue with sunrise/sunset/regular selector
* preset dropdowns
* sort before use
* delete timer when preset = 0
* truncate long names and add sort options
* html updates
* fix save bug
* make boxes a bit darker
* add fallback if localStorage is empty
* remove sort option
* code rabbit suggested fixes
* remove css
* restyling
* common
* dry preset loading
* revert missing const
* update deep sleep usermod with new timer struct
* tabs
* Revert "tabs"
This reverts commit fdc01f2f5d.
* remove unneeded clamp checks
* leave index alone
* remove useless check
* fix up await functions
* only fetch localstorage
* inline single line function
---------
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
391 lines
15 KiB
HTML
391 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
|
|
<meta charset="utf-8">
|
|
<title>Time Settings</title>
|
|
<style> html { visibility: hidden; } </style> <!-- prevent white & ugly display while loading, unhidden in loadResources() -->
|
|
<script>
|
|
var el=false;
|
|
var ms=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
|
|
var timerCount = 0;
|
|
var maxTimers;
|
|
var presets = {};
|
|
var sortedPresetOptions = '';
|
|
const TIMER_SUNRISE = 255;
|
|
const TIMER_SUNSET = 254;
|
|
const TIMER_WEEKDAYS_ALL = 255;
|
|
// 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() {
|
|
getLoc();
|
|
populatePresets(true);
|
|
loadJS(getURL('/settings/s.js?p=5'), false, ()=>{BTa();}, ()=>{
|
|
updLatLon();
|
|
Cs();
|
|
});
|
|
if (loc) d.Sf.action = getURL('/settings/time');
|
|
}
|
|
function populatePresets(fromls) {
|
|
let json = null;
|
|
if (fromls) {
|
|
try {
|
|
json = JSON.parse(localStorage.getItem("wledP"));
|
|
} catch (e) {}
|
|
}
|
|
|
|
if (!json) {
|
|
showToast("Please visit the main UI first to load presets", true);
|
|
return;
|
|
}
|
|
|
|
onPresetsLoaded(json);
|
|
}
|
|
function onPresetsLoaded(json) {
|
|
presets = json;
|
|
delete presets["0"];
|
|
bSPO();
|
|
pMP();
|
|
rTOPO();
|
|
rBPO();
|
|
}
|
|
function bSPO() { // buildSortedPresetOptions
|
|
sortedPresetOptions = '';
|
|
for (var p of Object.entries(presets)) {
|
|
var id = p[0];
|
|
var name = p[1].n || ("Preset " + id);
|
|
sortedPresetOptions += `<option value="${id}">${id}: ${name}</option>`;
|
|
}
|
|
}
|
|
function sPSV(sel, value, attrName) { // setPresetSelectValue
|
|
var v = (value === undefined || value === null || value === "") ? "0" : String(value);
|
|
sel.setAttribute(attrName, v);
|
|
sel.value = v;
|
|
if (sel.value === "") sel.value = "0";
|
|
}
|
|
function rPS(sel, presetOpts, attrName) { // refreshPresetSelect
|
|
var currentVal = sel.value;
|
|
var initialVal = sel.getAttribute(attrName);
|
|
var val = currentVal;
|
|
if ((val === "" || val === "0") && initialVal !== null && initialVal !== "" && initialVal !== "0") {
|
|
val = initialVal;
|
|
}
|
|
if (val === "") val = "0";
|
|
sel.innerHTML = presetOpts;
|
|
sel.value = val;
|
|
if (sel.value === "" && initialVal !== null && initialVal !== "") sel.value = initialVal;
|
|
if (sel.value === "") sel.value = "0";
|
|
}
|
|
function expand(o,i)
|
|
{
|
|
var t = gId("WD"+i);
|
|
t.style.display = t.style.display!=="none" ? "none" : "";
|
|
o.innerHTML = t.style.display==="none" ? "📅" : "✕";
|
|
}
|
|
function Cs() { gId("cac").style.display=(gN("OL").checked)?"block":"none"; }
|
|
function BTa()
|
|
{
|
|
timerCount = 0;
|
|
gId("TMT").innerHTML = "<thead><tr><th>En.</th><th>Type</th><th>Hour</th><th>Minute</th><th></th></tr></thead>";
|
|
}
|
|
function addTimerRow(hour, minute, preset, weekdays, monthStart, dayStart, monthEnd, dayEnd) {
|
|
if (timerCount >= maxTimers) return;
|
|
var i = timerCount++;
|
|
var isSunrise = (hour === TIMER_SUNRISE);
|
|
var isSunset = (hour === TIMER_SUNSET);
|
|
var isSpecial = isSunrise || isSunset;
|
|
if (hour === undefined) hour = 0;
|
|
if (minute === undefined) minute = 0;
|
|
if (preset === undefined) preset = 0;
|
|
if (weekdays === undefined) weekdays = TIMER_WEEKDAYS_ALL;
|
|
if (monthStart === undefined) monthStart = 1;
|
|
if (dayStart === undefined) dayStart = 1;
|
|
if (monthEnd === undefined) monthEnd = 12;
|
|
if (dayEnd === undefined) dayEnd = 31;
|
|
var enabled = weekdays & 1;
|
|
var dow = weekdays >> 1;
|
|
var container = gId("TMT");
|
|
var hourVal = isSpecial ? 0 : hour;
|
|
var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions;
|
|
|
|
var weekdayTable = '<table class="tw"><tr><th>M</th><th>T</th><th>W</th><th>T</th><th>F</th><th>S</th><th>S</th></tr><tr>';
|
|
for (j=1;j<8;j++) weekdayTable += `<td><input id="W${i}${j}" type="checkbox" ${(dow>>(j-1))&1?'checked':''}></td>`;
|
|
weekdayTable += '</tr></table>';
|
|
|
|
var dateRange = `<div id="DR${i}" class="tdr"><span class="tdf">from <select name="M${i}">`;
|
|
for (j=0;j<12;j++) dateRange += `<option value="${j+1}" ${monthStart==j+1?'selected':''}>${ms[j]}</option>`;
|
|
dateRange += `</select><input name="D${i}" class="xs" type="number" min="1" max="31" value="${dayStart}"></span><span class="tdf">to <select name="P${i}">`;
|
|
for (j=0;j<12;j++) dateRange += `<option value="${j+1}" ${monthEnd==j+1?'selected':''}>${ms[j]}</option>`;
|
|
dateRange += `</select><input name="E${i}" class="xs" type="number" min="1" max="31" value="${dayEnd}"></span></div>`;
|
|
|
|
var timerMain = `
|
|
<tr>
|
|
<td>
|
|
<input name="W${i}" id="W${i}" type="hidden">
|
|
<input id="W${i}0" type="checkbox" ${enabled ? "checked" : ""}>
|
|
</td><td>
|
|
<select id="TS${i}" class="s" onchange="TT(${i})">
|
|
<option value="0" ${!isSpecial ? "selected" : ""}>Regular</option>
|
|
<option value="${TIMER_SUNRISE}" ${isSunrise ? "selected" : ""}>Sunrise</option>
|
|
<option value="${TIMER_SUNSET}" ${isSunset ? "selected" : ""}>Sunset</option>
|
|
</select><br>
|
|
</td><td>
|
|
<input ${isSpecial ? "" : 'name="H'+i+'"'} id="H${i}" class="s" type="number" min="0" max="24" value="${hourVal}" ${isSpecial ? "disabled" : ""}>
|
|
</td>
|
|
<td><input name="N${i}" id="N${i}" class="l" type="number" min="${isSpecial ? -120 : 0}" max="${isSpecial ? 120 : 59}" value="${minute}"></td>
|
|
<td><div id="CB${i}" onclick="expand(this,${i})" class="cal">📅</div></td>
|
|
</tr><tr>
|
|
<td colspan="5">
|
|
<select name="T${i}" id="T${i}" class="s">${presetOpts}</select>
|
|
</td>
|
|
</tr><tr><td colspan="5"><hr></td></tr>
|
|
`;
|
|
|
|
var timerExpanded = `
|
|
<tr>
|
|
<td colspan="5">
|
|
<div id="WD${i}" style="display:none;background-color:#444;">
|
|
<hr>Run on weekdays
|
|
${weekdayTable}
|
|
${dateRange}
|
|
<hr>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
|
|
container.insertAdjacentHTML("beforeend", timerMain + timerExpanded);
|
|
var timerPresetSel = gId("T"+i);
|
|
sPSV(timerPresetSel, preset, "data-preset");
|
|
}
|
|
function TT(i) {
|
|
var sel = gId("TS"+i);
|
|
var hour = gId("H"+i);
|
|
var min = gId("N"+i);
|
|
var isSpecial = sel.value != 0;
|
|
hour.disabled = isSpecial;
|
|
if (isSpecial) {
|
|
// Save current hour value before switching to sunrise/sunset
|
|
hour.setAttribute("data-regular-hour", hour.value);
|
|
hour.removeAttribute("name");
|
|
min.min = -120;
|
|
min.max = 120;
|
|
} else {
|
|
// Restore saved hour value when switching back to regular
|
|
var savedHour = hour.getAttribute("data-regular-hour");
|
|
if (savedHour !== null && savedHour !== "") {
|
|
hour.value = savedHour;
|
|
}
|
|
hour.setAttribute("name", "H"+i);
|
|
min.min = 0;
|
|
min.max = 59;
|
|
if (min.value < 0 || min.value > 59) min.value = 0;
|
|
}
|
|
}
|
|
function pMP() { // populateMacroPresets
|
|
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
|
|
var fields = ['A0','A1','MC','MN'];
|
|
for (var f of fields) {
|
|
var inp = gN(f);
|
|
if (!inp) continue;
|
|
var val = inp.getAttribute("data-preset-value");
|
|
if (val === null || val === "") val = inp.value;
|
|
if (inp.tagName === "SELECT") {
|
|
sPSV(inp, val, "data-preset-value");
|
|
rPS(inp, presetOpts, "data-preset-value");
|
|
} else {
|
|
var sel = document.createElement('select');
|
|
sel.name = f;
|
|
sel.className = inp.className;
|
|
sel.required = inp.required;
|
|
sPSV(sel, val, "data-preset-value");
|
|
rPS(sel, presetOpts, "data-preset-value");
|
|
inp.parentNode.replaceChild(sel, inp);
|
|
}
|
|
}
|
|
}
|
|
function rTOPO() { // refreshTimerPresetOptions
|
|
var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions;
|
|
for (var i=0; i<timerCount; i++) {
|
|
var sel = gId("T"+i);
|
|
if (!sel) continue;
|
|
rPS(sel, presetOpts, "data-preset");
|
|
}
|
|
}
|
|
function rBPO() { // refreshButtonPresetOptions
|
|
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
|
|
var container = gId("macros");
|
|
if (!container) return;
|
|
var sels = container.querySelectorAll('select[name^="MP"],select[name^="ML"],select[name^="MD"]');
|
|
for (var sel of sels) {
|
|
rPS(sel, presetOpts, "data-preset");
|
|
}
|
|
}
|
|
function Wd()
|
|
{
|
|
for (i=0; i<timerCount; i++) {
|
|
var m=1, val=0;
|
|
for(j=0;j<8;j++) { val+=gId(("W"+i)+j).checked*m; m*=2;}
|
|
gId("W"+i).value=val;
|
|
var sel = gId("TS"+i);
|
|
var hour = gId("H"+i);
|
|
if (sel && sel.value != 0) {
|
|
// Re-add name attribute, remove disabled, and set value for sunrise/sunset before submission
|
|
hour.setAttribute("name", "H"+i);
|
|
hour.disabled = false;
|
|
hour.value = sel.value;
|
|
}
|
|
}
|
|
if (d.Sf.LTR.value==="S") { d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); }
|
|
if (d.Sf.LNR.value==="W") { d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); }
|
|
}
|
|
function addRow(i,p,l,d) {
|
|
var b = String.fromCharCode((i<10?48:55)+i);
|
|
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
|
|
var buttonBlock = document.createElement('div');
|
|
buttonBlock.className = 'bb';
|
|
buttonBlock.innerHTML = `
|
|
<div class="bh">Button ${i}</div>
|
|
<div class="bs">
|
|
<div class="ba">
|
|
<label>Push/Switch</label>
|
|
<select name="MP${b}" class="s" required>${presetOpts}</select>
|
|
</div>
|
|
<div class="ba">
|
|
<label>Short (on→off)</label>
|
|
<select name="ML${b}" class="s" required>${presetOpts}</select>
|
|
</div>
|
|
<div class="ba">
|
|
<label>Long (off→on)</label>
|
|
<select name="MD${b}" class="s" required>${presetOpts}</select>
|
|
</div>
|
|
</div>
|
|
<hr style="width:100%;margin:8px 0 0 0;">
|
|
`;
|
|
var buttonSels = buttonBlock.querySelectorAll("select");
|
|
var buttonVals = [String(p), String(l), String(d)];
|
|
for (var si=0; si<buttonSels.length; si++) {
|
|
sPSV(buttonSels[si], buttonVals[si], "data-preset");
|
|
}
|
|
gId("macros").appendChild(buttonBlock);
|
|
}
|
|
function getLatLon() {
|
|
if (!el) {
|
|
window.addEventListener("message", (event) => {
|
|
if (event.origin !== "https://locate.wled.me") return;
|
|
if (event.data instanceof Object) {
|
|
d.Sf.LT.value = event.data.lat;
|
|
d.Sf.LN.value = event.data.lon;
|
|
updLatLon();
|
|
}
|
|
}, false);
|
|
el = true;
|
|
}
|
|
window.open("https://locate.wled.me","_blank");
|
|
}
|
|
function updLatLon(i) {
|
|
if (parseFloat(d.Sf.LT.value)<0) { d.Sf.LTR.value = "S"; d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); } else d.Sf.LTR.value = "N";
|
|
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>
|
|
</head>
|
|
<body>
|
|
<form id="form_s" name="Sf" method="post" onsubmit="Wd()">
|
|
<div class="toprow">
|
|
<div class="helpB"><button type="button" onclick="H('features/settings/#time-settings')">?</button></div>
|
|
<button type="button" onclick="B()">Back</button><button type="submit">Save</button><hr>
|
|
</div>
|
|
<h2>Time setup</h2>
|
|
<div class="sec">
|
|
Get time from NTP server: <input type="checkbox" name="NT"><br>
|
|
<input type="text" name="NS" maxlength="32"><br>
|
|
Use 24h format: <input type="checkbox" name="CF"><br>
|
|
Time zone:
|
|
<select name="TZ">
|
|
<option value="0" selected>GMT(UTC)</option>
|
|
<option value="1">GMT/BST</option>
|
|
<option value="2">CET/CEST</option>
|
|
<option value="3">EET/EEST</option>
|
|
<option value="4">US-EST/EDT</option>
|
|
<option value="5">US-CST/CDT</option>
|
|
<option value="6">US-MST/MDT</option>
|
|
<option value="7">US-AZ</option>
|
|
<option value="8">US-PST/PDT</option>
|
|
<option value="9">CST (AWST, PHST)</option>
|
|
<option value="10">JST (KST)</option>
|
|
<option value="11">AEST/AEDT</option>
|
|
<option value="12">NZST/NZDT</option>
|
|
<option value="13">North Korea</option>
|
|
<option value="14">IST (India)</option>
|
|
<option value="15">CA-Saskatchewan</option>
|
|
<option value="16">ACST</option>
|
|
<option value="17">ACST/ACDT</option>
|
|
<option value="18">HST (Hawaii)</option>
|
|
<option value="19">NOVT (Novosibirsk)</option>
|
|
<option value="20">AKST/AKDT (Anchorage)</option>
|
|
<option value="21">MX-CST</option>
|
|
<option value="22">PKT (Pakistan)</option>
|
|
<option value="23">BRT (Brasília)</option>
|
|
<option value="24">AWST (Perth)</option>
|
|
</select><br>
|
|
UTC offset: <input name="UO" type="number" min="-65500" max="65500" required> seconds (max. 18 hours)<br>
|
|
Current local time is <span class="times">unknown</span>.<br>
|
|
Latitude: <select name="LTR"><option value="N">N</option><option value="S">S</option></select><input name="LT" type="number" class="xl" min="0" max="66.6" step="0.01"><br>
|
|
Longitude: <select name="LNR"><option value="E">E</option><option value="W">W</option></select><input name="LN" type="number" class="xl" min="0" max="180" step="0.01"><br>
|
|
<button type="button" id="locbtn" onclick="getLatLon()">Get location</button>
|
|
<div><i>(opens new tab, only works in browser)</i></div>
|
|
<div id="sun" class="times"></div>
|
|
</div>
|
|
<div class="sec">
|
|
<h3>Clock</h3>
|
|
Analog Clock overlay: <input type="checkbox" name="OL" onchange="Cs()"><br>
|
|
<div id="cac">
|
|
First LED: <input name="O1" type="number" min="0" max="1024" required> Last LED: <input name="O2" type="number" min="0" max="1024" required><br>
|
|
12h LED: <input name="OM" type="number" min="0" max="1024" required><br>
|
|
Show 5min marks: <input type="checkbox" name="O5"><br>
|
|
Seconds (as trail): <input type="checkbox" name="OS"><br>
|
|
Show clock overlay only if all LEDs are solid black: <input type="checkbox" name="OB"><br>
|
|
</div>
|
|
Countdown Mode: <input type="checkbox" name="CE"><br>
|
|
Countdown Goal:<br>
|
|
Date: <nowrap>20<input name="CY" class="xs" type="number" min="0" max="99" required>-<input name="CI" class="xs" type="number" min="1" max="12" required>-<input name="CD" class="xs" type="number" min="1" max="31" required></nowrap><br>
|
|
Time: <nowrap><input name="CH" class="xs" type="number" min="0" max="23" required>:<input name="CM" class="xs" type="number" min="0" max="59" required>:<input name="CS" class="xs" type="number" min="0" max="59" required></nowrap><br>
|
|
</div>
|
|
<hr>
|
|
<h2>Macro Presets</h2>
|
|
<i>Presets can be used as macros for both JSON and HTTP API commands.<br>
|
|
Enter the preset ID below.</i>
|
|
<i>Use 0 for the default action instead of a preset</i><br>
|
|
<a href="https://kno.wled.ge/interfaces/json-api/" target="_blank">JSON API</a><br>
|
|
<a href="https://kno.wled.ge/interfaces/http-api/" target="_blank">HTTP API</a><br>
|
|
<div class="sec">
|
|
<h3>Timer & Alexa Presets</h3>
|
|
Countdown-Over Preset: <input name="MC" class="m" type="number" min="0" max="250" required><br>
|
|
Timed-Light-Over Presets: <input name="MN" class="m" type="number" min="0" max="250" required><br>
|
|
Alexa On/Off Preset: <input name="A0" class="m" type="number" min="0" max="250" required> <input name="A1" class="m" type="number" min="0" max="250" required><br>
|
|
</div>
|
|
<div class="sec">
|
|
<h3>Button Action Presets</h3>
|
|
<div id="macros"></div>
|
|
<a href="https://kno.wled.ge/features/macros/#analog-button" target="_blank">Analog Button setup</a>
|
|
</div>
|
|
<div class="sec">
|
|
<h3>Time-Controlled Presets</h3>
|
|
<div style="display: inline-block">
|
|
<table id="TMT" style="min-width:330px;"></table>
|
|
<button type="button" onclick="addTimerRow()">Add Timer</button>
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
|
|
</form>
|
|
</body>
|
|
</html>
|