|
32 | 32 | json = JSON.parse(localStorage.getItem("wledP")); |
33 | 33 | } catch (e) {} |
34 | 34 | } |
35 | | - |
36 | 35 | 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 | + }); |
39 | 49 | } |
40 | | - |
41 | | - onPresetsLoaded(json); |
42 | 50 | } |
43 | 51 | function onPresetsLoaded(json) { |
44 | 52 | presets = json; |
|
85 | 93 | function BTa() |
86 | 94 | { |
87 | 95 | 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>"; |
89 | 97 | } |
90 | 98 | function addTimerRow(hour, minute, preset, weekdays, monthStart, dayStart, monthEnd, dayEnd) { |
91 | 99 | if (timerCount >= maxTimers) return; |
|
103 | 111 | if (dayEnd === undefined) dayEnd = 31; |
104 | 112 | var enabled = weekdays & 1; |
105 | 113 | var dow = weekdays >> 1; |
106 | | - var container = gId("TMT"); |
| 114 | + var container = gId("TMT").querySelector("table"); |
107 | 115 | var hourVal = isSpecial ? 0 : hour; |
108 | 116 | var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions; |
109 | 117 |
|
|
117 | 125 | for (j=0;j<12;j++) dateRange += `<option value="${j+1}" ${monthEnd==j+1?'selected':''}>${ms[j]}</option>`; |
118 | 126 | dateRange += `</select><input name="E${i}" class="xs" type="number" min="1" max="31" value="${dayEnd}"></span></div>`; |
119 | 127 |
|
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">📅</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">📅</div></td> |
| 144 | +</tr>`; |
142 | 145 |
|
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>`; |
155 | 156 |
|
156 | 157 | container.insertAdjacentHTML("beforeend", timerMain + timerExpanded); |
157 | 158 | var timerPresetSel = gId("T"+i); |
|
182 | 183 | } |
183 | 184 | } |
184 | 185 | function pMP() { // populateMacroPresets |
185 | | - var presetOpts = '<option value="0">Default Action</option>' + sortedPresetOptions; |
| 186 | + var presetOpts = '<option value="0">Default Action (0)</option>' + sortedPresetOptions; |
186 | 187 | var fields = ['A0','A1','MC','MN']; |
187 | 188 | for (var f of fields) { |
188 | 189 | var inp = gN(f); |
|
202 | 203 | inp.parentNode.replaceChild(sel, inp); |
203 | 204 | } |
204 | 205 | } |
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 | | - }); |
231 | 206 | } |
232 | 207 | function rTOPO() { // refreshTimerPresetOptions |
233 | 208 | var presetOpts = '<option value="0">Delete Timer</option>' + sortedPresetOptions; |
|
237 | 212 | rPS(sel, presetOpts, "data-preset"); |
238 | 213 | } |
239 | 214 | } |
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; |
248 | 220 | } |
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'; |
255 | 234 | } |
256 | 235 | } |
257 | 236 | 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(); |
259 | 239 | var container = gId("macros"); |
260 | 240 | if (!container) return; |
| 241 | + // analog buttons only have an MD select (MP/ML are hidden 0 inputs); MD uses analog options, never presets |
261 | 242 | var sels = container.querySelectorAll('select[name^="MP"],select[name^="ML"],select[name^="MD"]'); |
262 | 243 | 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"); |
264 | 247 | } |
265 | 248 | } |
266 | 249 | function TZ() |
|
290 | 273 | if (d.Sf.LTR.value==="S") { d.Sf.LT.value = -1*parseFloat(d.Sf.LT.value); } |
291 | 274 | if (d.Sf.LNR.value==="W") { d.Sf.LN.value = -1*parseFloat(d.Sf.LN.value); } |
292 | 275 | } |
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); |
297 | 281 | var buttonBlock = document.createElement('div'); |
298 | 282 | 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> |
300 | 288 | <div class="bs"> |
301 | 289 | <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> |
304 | 292 | </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"> |
305 | 302 | <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> |
308 | 305 | </div> |
309 | 306 | <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> |
312 | 310 | </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 | + } |
319 | 343 | } |
| 344 | + gId("macros").appendChild(buttonBlock); |
320 | 345 | } |
321 | 346 | function getLatLon() { |
322 | 347 | if (!el) { |
@@ -384,9 +409,10 @@ <h3>Button actions</h3> |
384 | 409 | <a href="#" onclick="H('/features/macros/#analog-button')">Analog Button setup</a> |
385 | 410 | <h3>Time-controlled presets</h3> |
386 | 411 | <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> |
388 | 413 | <hr> |
389 | 414 | <button type="button" onclick="B()">Back</button><button type="submit">Save</button> |
390 | 415 | </form> |
| 416 | + <div id="toast"></div> |
391 | 417 | </body> |
392 | 418 | </html> |
0 commit comments