@@ -48,7 +48,7 @@ <h1>Settings</h1>
4848
4949 <!-- Settings -->
5050 < form action ="/save_config " method ="POST ">
51- < details >
51+ < details ontoggle =" loadSubScript('gpio', 'setupPinValidator');loadSubScript('calibration', 'setupCalibration'); " >
5252 < summary role ="button " class ="outline secondary "> LED Hardware</ summary >
5353 < div >
5454 < label > LED type
@@ -60,11 +60,11 @@ <h1>Settings</h1>
6060 </ label >
6161
6262 < label > Data Pin (GPIO)
63- < select type =" number " name ="dataPin " required > </ select >
63+ < select name ="dataPin " required > </ select >
6464 </ label >
6565
6666 < label id ="clockPinLabel "> Clock Pin (GPIO)
67- < select type =" number " name ="clockPin "> </ select >
67+ < select name ="clockPin "> </ select >
6868 </ label >
6969
7070 < label > Number of LEDs
@@ -79,8 +79,8 @@ <h5>White channel calibration</h5>
7979 < label > G < input type ="number " name ="calGreen " min ="0 " max ="255 " value ="160 "> </ label >
8080 < label > B < input type ="number " name ="calBlue " min ="0 " max ="255 " value ="160 "> </ label >
8181 < label > White < input type ="number " name ="calGain " min ="0 " max ="255 " value ="255 "> </ label >
82- < button type ="button " onclick =" setCalibration(255, 160, 160, 160) "> Set Cold</ button >
83- < button type ="button " onclick =" setCalibration(255, 176, 176, 112) "> Set Neutral</ button >
82+ < button type ="button " id =" cal-cold "> Set Cold</ button >
83+ < button type ="button " id =" cal-neutral "> Set Neutral</ button >
8484 </ div >
8585 </ div >
8686 </ div >
@@ -119,7 +119,7 @@ <h5>White channel calibration</h5>
119119 </ div >
120120 </ details >
121121
122- < details id ="ota_page " style ="display: none; " ontoggle ="loadSubScript(this, 'ota', null) ">
122+ < details id ="ota_page " style ="display: none; " ontoggle ="loadSubScript('ota', null) ">
123123 < summary role ="button " class ="outline secondary "> Firmware Update (OTA)</ summary >
124124 < div >
125125 < div style ="margin-bottom: 1.5rem; padding: 1rem; border: 1px solid var(--pico-form-element-border-color); border-radius: var(--pico-border-radius); text-align: center; background: rgba(128, 128, 128, 0.05); ">
@@ -185,8 +185,8 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
185185 let cfgDeviceVersion = "" ;
186186
187187 const loadedScripts = { } ;
188- function loadSubScript ( el , scriptName , initFunName ) {
189- if ( el . open && ! loadedScripts [ scriptName ] ) {
188+ function loadSubScript ( scriptName , initFunName ) {
189+ if ( ! loadedScripts [ scriptName ] ) {
190190 console . log ( `Loading module: ${ scriptName } ` ) ;
191191
192192 const script = document . createElement ( 'script' ) ;
@@ -208,99 +208,6 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
208208 }
209209 }
210210
211- function setupPinValidator ( ) {
212- const hardwareLimits = {
213- "ESP32-C6" : { gpio : [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 10 , 15 , 18 , 19 , 20 , 21 , 22 ] , spi : { 6 :5 } } ,
214- "ESP32-S3" : { gpio : [ 1 , 2 , 4 , 5 , 6 , 7 , 8 , 10 , 16 , 17 , 18 , 48 ] , spi : { 11 :12 } } ,
215- "ESP32-C3" : { gpio : [ 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 10 , 20 , 21 ] , spi : { 7 :6 } } ,
216- "ESP8266" : { gpio : [ 2 ] , spi : { 19 :18 } } ,
217- "ESP32" : { gpio : null , spi : { 23 :18 } } ,
218- "ESP32-S2" : { gpio : null , spi : { 19 :18 } }
219- } ;
220-
221- const arch = ( typeof cfgDeviceArchitecture !== 'undefined' ) ? cfgDeviceArchitecture : "" ;
222- const els = { type : document . getElementById ( 'ledType' ) , clkLabel : document . getElementById ( 'clockPinLabel' ) } ;
223-
224- if ( ! els . type || ! els . clkLabel ) {
225- console . warn ( "LED Validator: Missing required DOM elements (ledType/clockPinLabel)" ) ;
226- return ;
227- }
228-
229- els . clkLabel . setAttribute ( 'aria-live' , 'polite' ) ;
230-
231- const setField = ( name , opts ) => {
232- const old = document . getElementsByName ( name ) [ 0 ] ;
233- if ( ! old ) return null ;
234-
235- const isSel = ( opts != null ) ;
236- const signature = "sig_" + JSON . stringify ( opts ) ;
237-
238- if ( ( old . tagName === 'SELECT' ) === isSel && old . dataset . sig === signature ) return old ;
239-
240- const wasFocused = ( document . activeElement === old ) ;
241-
242- const el = isSel ? document . createElement ( 'select' ) : Object . assign ( document . createElement ( 'input' ) , {
243- type : 'number' , min : 0 , max : ( arch . includes ( '8266' ) ? 16 : 48 ) , step : '1'
244- } ) ;
245-
246- el . name = name ; el . id = old . id ; el . required = true ;
247- el . dataset . sig = signature ;
248-
249- if ( isSel ) {
250- opts . forEach ( p => el . add ( new Option ( `GPIO ${ p } ` , p ) ) ) ;
251- el . value = opts . includes ( parseInt ( old . value ) ) ? old . value : opts [ 0 ] ;
252-
253- if ( opts . length === 1 ) {
254- el . disabled = true ;
255- el . title = "Only one valid GPIO for this mode/architecture" ;
256- }
257- } else {
258- el . value = old . value || 0 ;
259- }
260-
261- old . replaceWith ( el ) ;
262- el . addEventListener ( 'change' , updateUI ) ;
263-
264- if ( wasFocused ) el . focus ( ) ;
265- return el ;
266- } ;
267-
268- function updateUI ( ) {
269- const isSpi = els . type . value == "2" ;
270- const cfg = hardwareLimits [ arch ] ;
271-
272- let validPins = cfg ? ( isSpi ? Object . keys ( cfg . spi ) . map ( Number ) : cfg . gpio ) : null ;
273- const dataPinEditor = setField ( 'dataPin' , validPins ) ;
274-
275- const autoClk = ( cfg && cfg . spi ) ? ( cfg . spi [ dataPinEditor . value ] ?? null ) : null ;
276- const clockPinEditor = setField ( 'clockPin' , ( ( autoClk !== null ) ? [ autoClk ] : null ) ) ;
277-
278- els . clkLabel . style . display = isSpi ? 'block' : 'none' ;
279- clockPinEditor . disabled = clockPinEditor . disabled || ! isSpi ;
280- }
281-
282- els . type . onchange = updateUI ;
283- updateUI ( ) ;
284- }
285-
286- function setCalibration ( gain , r , g , b ) {
287- const fields = { 'calGain' : gain , 'calRed' : r , 'calGreen' : g , 'calBlue' : b } ;
288-
289- for ( const [ name , value ] of Object . entries ( fields ) ) {
290- const el = document . querySelector ( `input[name="${ name } "]` ) ;
291- if ( el ) {
292- el . value = value ;
293- }
294- }
295- }
296-
297- function toggleCalibration ( ) {
298- const ledTypeSelect = document . getElementById ( 'ledType' ) ;
299- const calSection = document . getElementById ( 'whiteCalibration' ) ;
300-
301- calSection . style . display = ( ledTypeSelect . value === "1" ) ? "block" : "none" ;
302- }
303-
304211 function showToast ( isReboot ) {
305212 const t = document . getElementById ( 'toast' ) ;
306213 const title = document . getElementById ( 'toast-title' ) ;
@@ -355,7 +262,9 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
355262 if ( el && l [ field ] !== undefined ) {
356263 if ( field == 'dataPin' || field == 'clockPin' )
357264 {
358- el . add ( new Option ( `GPIO ${ l [ field ] } ` , l [ field ] ) ) ;
265+ if ( el . tagName === 'SELECT' && ! ( Array . from ( el . options ) . some ( opt => String ( opt . value ) === String ( l [ field ] ) ) ) ) {
266+ el . add ( new Option ( `GPIO ${ l [ field ] } ` , l [ field ] ) ) ;
267+ }
359268 }
360269 el . value = l [ field ] ;
361270 el . dispatchEvent ( new Event ( 'input' ) ) ;
@@ -364,40 +273,25 @@ <h3>⚠️ OTA Firmware Flash Procedure</h3>
364273
365274 if ( l [ "apMode" ] === true ) {
366275 document . getElementById ( "wifi_configuration_page" ) . style . display = "block" ;
367- loadSubScript ( this , 'wifi' , "scanWifi " ) ;
276+ loadSubScript ( 'wifi' , "setupWifi " ) ;
368277 } else {
369278 document . getElementById ( "ota_page" ) . style . display = "block" ;
370279 }
371280
372- toggleCalibration ( ) ;
373- setupPinValidator ( ) ;
281+ if ( typeof toggleCalibration === "function" ) {
282+ toggleCalibration ( ) ;
283+ }
284+
285+ if ( typeof setupPinValidator === "function" ) {
286+ setupPinValidator ( ) ;
287+ }
374288 }
375289 catch ( e ) {
376290 console . error ( "Config load failed:" , e ) ;
377291 }
378292 }
379293
380- document . addEventListener ( 'DOMContentLoaded' , ( ) => {
381- const select = document . getElementById ( 'ssid_select' ) ;
382- const customDiv = document . getElementById ( 'custom_ssid_wrapper' ) ;
383- const customInput = document . getElementById ( 'ssid_custom' ) ;
384- const ledTypeSelect = document . getElementById ( 'ledType' ) ;
385-
386- select . addEventListener ( 'change' , ( ) => {
387- if ( select . value === 'CUSTOM' ) {
388- customDiv . style . display = 'block' ;
389- setTimeout ( ( ) => customInput . focus ( ) , 100 ) ;
390- customInput . required = true ;
391- }
392- else {
393- customDiv . style . display = 'none' ;
394- customInput . required = false ;
395- customInput . value = '' ;
396- }
397- } ) ;
398-
399- ledTypeSelect . addEventListener ( 'change' , toggleCalibration ) ;
400-
294+ document . addEventListener ( 'DOMContentLoaded' , ( ) => {
401295 loadCurrentConfig ( ) ;
402296 } ) ;
403297
0 commit comments