Skip to content

Commit 7158e3a

Browse files
committed
Bugfixes & improvements
1 parent 0faccd2 commit 7158e3a

6 files changed

Lines changed: 142 additions & 120 deletions

File tree

wled00/data/common.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ function isN(n) { return !isNaN(parseFloat(n)) && isFinite(n); } // isNumber
1515
// https://stackoverflow.com/questions/3885817/how-do-i-check-that-a-number-is-float-or-integer
1616
function isF(n) { return n === +n && n !== (n|0); } // isFloat
1717
function isI(n) { return n === +n && n === (n|0); } // isInteger
18+
function chrID(x) { return String.fromCharCode((x<10?48:55)+x); }
1819
function toggle(el) { gId(el).classList.toggle("hide"); let n = gId('No'+el); if (n) n.classList.toggle("hide"); }
1920
function tooltip(cont=null) {
2021
qSA((cont?cont+" ":"")+"[title]").forEach((element)=>{

wled00/data/settings_leds.htm

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
function mustR(t) { return !!(gT(t).c & 0x20); } // Off refresh is mandatory
3232
function numPins(t){ return Math.max(gT(t).t.length, 1); } // type length determines number of GPIO pins
3333
function numChan(t){ return 3*hasRGB(t) + hasW(t) + hasCCT(t); }
34-
function chrID(x) { return String.fromCharCode((x<10?48:55)+x); }
3534
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)
3635
function S() {
3736
getLoc();

wled00/data/settings_time.htm

Lines changed: 128 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,21 @@
3232
json = JSON.parse(localStorage.getItem("wledP"));
3333
} catch (e) {}
3434
}
35-
3635
if (!json) {
37-
showToast("Please visit the main UI first to load presets", true);
38-
return;
36+
fetch(getURL(`/presets.json`), {
37+
method: 'get'
38+
})
39+
.then(res => {
40+
if (!res.ok) showToast("Error loading presets.", true);
41+
return res.json();
42+
})
43+
.then(json => {
44+
onPresetsLoaded(json);
45+
})
46+
.catch((error)=>{
47+
showToast("Loading presets failed.", true);
48+
});
3949
}
40-
41-
onPresetsLoaded(json);
4250
}
4351
function onPresetsLoaded(json) {
4452
presets = json;
@@ -85,7 +93,7 @@
8593
function BTa()
8694
{
8795
timerCount = 0;
88-
gId("TMT").innerHTML = "<thead><tr><th>En.</th><th>Type</th><th>Hour</th><th>Minute</th><th></th></tr></thead>";
96+
gId("TMT").innerHTML = "<table><thead><tr><th>En.</th><th>Type</th><th>Hour</th><th>Minute</th><th>Preset</th><th></th></tr></thead></table>";
8997
}
9098
function addTimerRow(hour, minute, preset, weekdays, monthStart, dayStart, monthEnd, dayEnd) {
9199
if (timerCount >= maxTimers) return;
@@ -103,7 +111,7 @@
103111
if (dayEnd === undefined) dayEnd = 31;
104112
var enabled = weekdays & 1;
105113
var dow = weekdays >> 1;
106-
var container = gId("TMT");
114+
var container = gId("TMT").querySelector("table");
107115
var hourVal = isSpecial ? 0 : hour;
108116
var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions;
109117

@@ -117,41 +125,34 @@
117125
for (j=0;j<12;j++) dateRange += `<option value="${j+1}" ${monthEnd==j+1?'selected':''}>${ms[j]}</option>`;
118126
dateRange += `</select><input name="E${i}" class="xs" type="number" min="1" max="31" value="${dayEnd}"></span></div>`;
119127

120-
var timerMain = `
121-
<tr>
122-
<td>
123-
<input name="W${i}" id="W${i}" type="hidden">
124-
<input id="W${i}0" type="checkbox" ${enabled ? "checked" : ""}>
125-
</td><td>
126-
<select id="TS${i}" class="s" onchange="TT(${i})">
127-
<option value="0" ${!isSpecial ? "selected" : ""}>Regular</option>
128-
<option value="${TIMER_SUNRISE}" ${isSunrise ? "selected" : ""}>Sunrise</option>
129-
<option value="${TIMER_SUNSET}" ${isSunset ? "selected" : ""}>Sunset</option>
130-
</select><br>
131-
</td><td>
132-
<input ${isSpecial ? "" : 'name="H'+i+'"'} id="H${i}" class="s" type="number" min="0" max="24" value="${hourVal}" ${isSpecial ? "disabled" : ""}>
133-
</td>
134-
<td><input name="N${i}" id="N${i}" class="l" type="number" min="${isSpecial ? -120 : 0}" max="${isSpecial ? 120 : 59}" value="${minute}"></td>
135-
<td><div id="CB${i}" onclick="expand(this,${i})" class="cal">&#128197;</div></td>
136-
</tr><tr>
137-
<td colspan="5">
138-
<select name="T${i}" id="T${i}" class="s">${presetOpts}</select>
139-
</td>
140-
</tr><tr><td colspan="5"><hr></td></tr>
141-
`;
128+
var timerMain = `<tr>
129+
<td>
130+
<input name="W${i}" id="W${i}" type="hidden">
131+
<input id="W${i}0" type="checkbox" ${enabled ? "checked" : ""}>
132+
</td>
133+
<td>
134+
<select id="TS${i}" class="s" onchange="TT(${i})">
135+
<option value="0" ${!isSpecial ? "selected" : ""}>Regular</option>
136+
<option value="${TIMER_SUNRISE}" ${isSunrise ? "selected" : ""}>Sunrise</option>
137+
<option value="${TIMER_SUNSET}" ${isSunset ? "selected" : ""}>Sunset</option>
138+
</select><br>
139+
</td>
140+
<td><input ${isSpecial ? "" : 'name="H'+i+'"'} id="H${i}" class="s" type="number" min="0" max="24" value="${hourVal}" ${isSpecial ? "disabled" : ""}></td>
141+
<td><input name="N${i}" id="N${i}" class="l" type="number" min="${isSpecial ? -120 : 0}" max="${isSpecial ? 120 : 59}" value="${minute}"></td>
142+
<td><select name="T${i}" id="T${i}" class="el">${presetOpts}</select></td>
143+
<td><div id="CB${i}" onclick="expand(this,${i})" class="cal">&#128197;</div></td>
144+
</tr>`;
142145

143-
var timerExpanded = `
144-
<tr>
145-
<td colspan="5">
146-
<div id="WD${i}" style="display:none;background-color:#444;">
147-
<hr>Run on weekdays
148-
${weekdayTable}
149-
${dateRange}
150-
<hr>
151-
</div>
152-
</td>
153-
</tr>
154-
`;
146+
var timerExpanded = `<tr>
147+
<td colspan="6">
148+
<div id="WD${i}" style="display:none;background-color:#444;">
149+
<hr>Run on weekdays
150+
${weekdayTable}
151+
${dateRange}
152+
<hr>
153+
</div>
154+
</td>
155+
</tr>`;
155156

156157
container.insertAdjacentHTML("beforeend", timerMain + timerExpanded);
157158
var timerPresetSel = gId("T"+i);
@@ -182,7 +183,7 @@
182183
}
183184
}
184185
function pMP() { // populateMacroPresets
185-
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
186+
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
186187
var fields = ['A0','A1','MC','MN'];
187188
for (var f of fields) {
188189
var inp = gN(f);
@@ -202,32 +203,6 @@
202203
inp.parentNode.replaceChild(sel, inp);
203204
}
204205
}
205-
populateMacroPresets();
206-
}
207-
function populateMacroPresets() {
208-
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
209-
var fields = ['A0','A1','MC','MN'];
210-
for (var f of fields) {
211-
var inp = gN(f);
212-
if (!inp) continue;
213-
var val = inp.value || 0;
214-
var sel = document.createElement('select');
215-
sel.name = f;
216-
sel.className = inp.className;
217-
sel.required = inp.required;
218-
sel.innerHTML = presetOpts;
219-
sel.value = val;
220-
inp.parentNode.replaceChild(sel, inp);
221-
}
222-
}
223-
function TZ()
224-
{
225-
zones.forEach((n,i)=>{
226-
let o = cE("option");
227-
o.value = i;
228-
o.text = n;
229-
d.Sf["TZ"].appendChild(o);
230-
});
231206
}
232207
function rTOPO() { // refreshTimerPresetOptions
233208
var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions;
@@ -237,30 +212,38 @@
237212
rPS(sel, presetOpts, "data-preset");
238213
}
239214
}
240-
function rBPO() { // refreshButtonPresetOptions
241-
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
242-
var container = gId("macros");
243-
if (!container) return;
244-
var sels = container.querySelectorAll('select[name^="MP"],select[name^="ML"],select[name^="MD"]');
245-
for (var sel of sels) {
246-
rPS(sel, presetOpts, "data-preset");
247-
}
215+
function bAO() { // buildAnalogOptions: analog functions + per-segment opacity (segment 0 included; MD=0 => segment 0)
216+
var o = '<optgroup label="Analog Functions"><option value="250">Global brightness (250)</option><option value="249">Effect speed (249)</option><option value="248">Effect intensity (248)</option><option value="247">Palette (247)</option><option value="200">Primary color hue (200)</option></optgroup><optgroup label="Analog Segment Opacity">';
217+
for (var j=0; j<=32; j++) o += `<option value="${j}">Segment ${j} opacity</option>`;
218+
o += '</optgroup>';
219+
return o;
248220
}
249-
function rTOPO() { // refreshTimerPresetOptions
250-
var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions;
251-
for (var i=0; i<timerCount; i++) {
252-
var sel = gId("T"+i);
253-
if (!sel) continue;
254-
rPS(sel, presetOpts, "data-preset");
221+
function isAnalogBtn(t) { return t==7 || t==8; } // BTN_TYPE_ANALOG / BTN_TYPE_ANALOG_INVERTED
222+
function isSwitchBtn(t) { return t==4 || t==5 || t==9; } // BTN_TYPE_SWITCH / BTN_TYPE_PIR_SENSOR / BTN_TYPE_TOUCH_SWITCH
223+
function btnTypeName(t) { // mirrors the button type dropdown on the LED settings page
224+
switch (+t) {
225+
case 2: return 'Pushbutton';
226+
case 3: return 'Push inverted';
227+
case 4: return 'Switch';
228+
case 5: return 'PIR sensor';
229+
case 6: return 'Touch';
230+
case 7: return 'Analog';
231+
case 8: return 'Analog inverted';
232+
case 9: return 'Touch (switch)';
233+
default: return 'Disabled';
255234
}
256235
}
257236
function rBPO() { // refreshButtonPresetOptions
258-
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
237+
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
238+
var analogOpts = bAO();
259239
var container = gId("macros");
260240
if (!container) return;
241+
// analog buttons only have an MD select (MP/ML are hidden 0 inputs); MD uses analog options, never presets
261242
var sels = container.querySelectorAll('select[name^="MP"],select[name^="ML"],select[name^="MD"]');
262243
for (var sel of sels) {
263-
rPS(sel, presetOpts, "data-preset");
244+
var bb = sel.closest ? sel.closest(".bb") : null;
245+
var t = bb ? parseInt(bb.getAttribute("data-btype")||"0",10) : 0;
246+
rPS(sel, isAnalogBtn(t) ? analogOpts : presetOpts, "data-preset");
264247
}
265248
}
266249
function TZ()
@@ -290,33 +273,75 @@
290273
if (d.Sf.LTR.value==="S") { d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); }
291274
if (d.Sf.LNR.value==="W") { d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); }
292275
}
293-
function addRow(i,p,l,d) {
294-
var container = gId("macros");
295-
var b = String.fromCharCode((i<10?48:55)+i);
296-
var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions;
276+
function addRow(i,p,l,d,t) {
277+
if (t===undefined) t = 0;
278+
var b = chrID(i);
279+
var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions;
280+
var typeName = btnTypeName(t);
297281
var buttonBlock = document.createElement('div');
298282
buttonBlock.className = 'bb';
299-
buttonBlock.innerHTML = `<div class="bh">Button ${i}</div>
283+
buttonBlock.setAttribute('data-btype', t); // read back by rBPO() to rebuild selects correctly
284+
if (isAnalogBtn(t)) {
285+
// analog buttons: MD holds the function/segment; short/long press are unused (firmware defaults missing MP/ML to 0)
286+
buttonBlock.innerHTML = `
287+
<div class="bh">Analog ${i} - ${typeName}</div>
300288
<div class="bs">
301289
<div class="ba">
302-
<label>Push/Switch</label>
303-
<select name="MP${b}" class="s" required>${presetOpts.replace(`value="${p}"`, `value="${p}" selected`)}</select>
290+
<label>Analog function</label>
291+
<select name="MD${b}" class="s" required>${bAO()}</select>
304292
</div>
293+
</div>
294+
<hr style="width:100%;margin:8px 0 0 0;">
295+
`;
296+
sPSV(buttonBlock.querySelector('select[name="MD'+b+'"]'), String(d), "data-preset");
297+
} else if (isSwitchBtn(t)) {
298+
// switches: MP fires on On->Off, ML on Off->On; double press (MD) is unused (firmware defaults missing MD to 0)
299+
buttonBlock.innerHTML = `
300+
<div class="bh">Switch ${i} - ${typeName}</div>
301+
<div class="bs">
305302
<div class="ba">
306-
<label>Short (on→off)</label>
307-
<select name="ML${b}" class="s" required>${presetOpts.replace(`value="${l}"`, `value="${l}" selected`)}</select>
303+
<label>On → Off</label>
304+
<select name="MP${b}" class="s" required>${presetOpts}</select>
308305
</div>
309306
<div class="ba">
310-
<label>Long (off→on)</label>
311-
<select name="MD${b}" class="s" required>${presetOpts.replace(`value="${d}"`, `value="${d}" selected`)}</select>
307+
<label>Off → On</label>
308+
<select name="ML${b}" class="s" required>${presetOpts}</select>
309+
</div>
312310
</div>
313-
</div>`;
314-
container.appendChild(buttonBlock);
315-
var buttonSels = buttonBlock.querySelectorAll("select");
316-
var buttonVals = [String(p), String(l), String(d)];
317-
for (var si=0; si<buttonSels.length; si++) {
318-
sPSV(buttonSels[si], buttonVals[si], "data-preset");
311+
<hr style="width:100%;margin:8px 0 0 0;">
312+
`;
313+
var switchSels = buttonBlock.querySelectorAll("select");
314+
var switchVals = [String(p), String(l)];
315+
for (var si=0; si<switchSels.length; si++) {
316+
sPSV(switchSels[si], switchVals[si], "data-preset");
317+
}
318+
} else {
319+
// pushbuttons: short (MP), long (ML) and double (MD) press
320+
buttonBlock.innerHTML = `
321+
<div class="bh">Button ${i} - ${typeName}</div>
322+
<div class="bs">
323+
<div class="ba">
324+
<label>Short press</label>
325+
<select name="MP${b}" class="s" required>${presetOpts}</select>
326+
</div>
327+
<div class="ba">
328+
<label>Long press</label>
329+
<select name="ML${b}" class="s" required>${presetOpts}</select>
330+
</div>
331+
<div class="ba">
332+
<label>Double press</label>
333+
<select name="MD${b}" class="s" required>${presetOpts}</select>
334+
</div>
335+
</div>
336+
<hr style="width:100%;margin:8px 0 0 0;">
337+
`;
338+
var buttonSels = buttonBlock.querySelectorAll("select");
339+
var buttonVals = [String(p), String(l), String(d)];
340+
for (var si=0; si<buttonSels.length; si++) {
341+
sPSV(buttonSels[si], buttonVals[si], "data-preset");
342+
}
319343
}
344+
gId("macros").appendChild(buttonBlock);
320345
}
321346
function getLatLon() {
322347
if (!el) {
@@ -384,9 +409,10 @@ <h3>Button actions</h3>
384409
<a href="#" onclick="H('/features/macros/#analog-button')">Analog Button setup</a>
385410
<h3>Time-controlled presets</h3>
386411
<div id="TMT" class="tmc"></div>
387-
<button type="button" onclick="addTimerRow()">Add Timer</button>
412+
<button type="button" title="Add timer row" onclick="addTimerRow()">+</button>
388413
<hr>
389414
<button type="button" onclick="B()">Back</button><button type="submit">Save</button>
390415
</form>
416+
<div id="toast"></div>
391417
</body>
392418
</html>

wled00/data/style.css

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ input[type="password"],
8888
select {
8989
font-size: medium;
9090
margin: 2px;
91+
background: #333;
92+
color: #fff;
93+
font-family: Verdana, sans-serif;
94+
border: 0.5ch solid #333;
9195
}
9296
input[type="number"] {
9397
width: 4em;
@@ -120,15 +124,8 @@ td input[type="checkbox"] {
120124
input[type=file] {
121125
font-size: 16px
122126
}
123-
select {
124-
margin: 2px;
125-
background: #333;
126-
color: #fff;
127-
font-family: Verdana, sans-serif;
128-
border: 0.5ch solid #333;
129-
}
130-
select.pin {
131-
max-width: 120px;
127+
select.el, select.pin {
128+
max-width: 150px;
132129
text-overflow: ellipsis;
133130
}
134131
tr {
@@ -145,7 +142,8 @@ td {
145142
cursor:pointer
146143
}
147144
/* Timer layout - tmc=container, tb=block, tm=main, ts=section, tf=field, tfe=enabled field, te=expanded, tw=weekdays, tdr=daterange, tdf=date from/to, tc=calendar, twd=weekday text */
148-
.tmc{display:flex;flex-direction:column;gap:12px;max-width:800px;margin:0 auto}
145+
.tmc{display:flex;flex-direction:column;gap:12px;max-width:640px;margin:0 auto}
146+
.tmc td{padding:0;margin:0;}
149147
.tb{background:#282828;border:2px solid #333;border-radius:8px;padding:12px}
150148
.tm{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-end}
151149
.ts{display:flex;flex-wrap:nowrap;gap:8px;align-items:flex-end}
@@ -165,7 +163,7 @@ td {
165163
.tdf{display:inline-flex;flex-wrap:nowrap;gap:4px;align-items:center;white-space:nowrap}
166164

167165
/* Button actions - bc=container, bb=block, bh=header, bs=actions, ba=action */
168-
.bc{display:flex;flex-wrap:wrap;gap:16px;justify-content:center;margin:16px 0}
166+
.bc{display:flex;flex-wrap:wrap;gap:16px;justify-content:center;margin:16px auto;max-width:640px;}
169167
.bb{background:#282828;border:2px solid #333;border-radius:8px;padding:12px;min-width:200px;flex:1 1 auto;max-width:300px}
170168
.bh{font-weight:bold;font-size:1.1em;margin-bottom:12px;color:#28f;text-align:center}
171169
.bs{display:flex;flex-direction:column;gap:8px}

wled00/ntp.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -532,8 +532,7 @@ void checkTimers()
532532
lastTimerMinute = minute(localTime);
533533
if (!hour(localTime) && minute(localTime)==1) calculateSunriseAndSunset();
534534
DEBUG_PRINTF_P(PSTR("Local time: %02d:%02d\n"), hour(localTime), minute(localTime));
535-
for (size_t i = 0; i < timers.size(); i++) {
536-
const Timer& t = timers[i];
535+
for (const Timer& t : timers) {
537536
if (!t.isEnabled()) continue;
538537
time_t tt = 0;
539538
if (t.isSunrise()) {
@@ -560,7 +559,7 @@ void checkTimers()
560559
#ifdef WLED_DEBUG
561560
if (t.isSunrise()) DEBUG_PRINTF_P(PSTR("Sunrise timer %d offset %d\n"), t.preset, t.minute);
562561
else if (t.isSunset()) DEBUG_PRINTF_P(PSTR("Sunset timer %d offset %d\n"), t.preset, t.minute);
563-
else DEBUG_PRINTF_P(PSTR("Timer %d: preset %d\n"), i, t.preset);
562+
else DEBUG_PRINTF_P(PSTR("Timer preset %d\n"), t.preset);
564563
#endif
565564
}
566565
}

0 commit comments

Comments
 (0)