@@ -28,6 +28,36 @@ norn_config.initialize = function (callback) {
2828 function update_ui ( ) {
2929 i18n . localizePage ( ) ;
3030
31+ // Populate Flight Controller list (explicit options)
32+ const fcSelect = $ ( "select[name='norn_fc']" ) ;
33+ if ( fcSelect . length ) {
34+ fcSelect . empty ( ) ;
35+ fcSelect . append ( `<option value="">${ i18n . getMessage ( "nornNone" ) } </option>` ) ;
36+ fcSelect . append ( `<option value="f4">FLASHHOBBYF405</option>` ) ;
37+ fcSelect . append ( `<option value="f7">SPEEDYBEEF405V3</option>` ) ;
38+ fcSelect . append ( `<option value="h7">TAKERF722SE</option>` ) ;
39+ fcSelect . append ( `<option value="h7">GEPRCF722</option>` ) ;
40+ fcSelect . on ( "change" , function ( ) {
41+ self . analyticsChanges [ "NornFC" ] = $ ( this ) . val ( ) || null ;
42+ } ) ;
43+ }
44+
45+ // Populate Drone Size list (explicit options)
46+ const droneSizeSelect = $ ( "select[name='norn_drone_size']" ) ;
47+ if ( droneSizeSelect . length ) {
48+ droneSizeSelect . empty ( ) ;
49+ droneSizeSelect . append ( `<option value="">${ i18n . getMessage ( "nornNone" ) } </option>` ) ;
50+ droneSizeSelect . append ( `<option value="7">7</option>` ) ;
51+ droneSizeSelect . append ( `<option value="8">8</option>` ) ;
52+ droneSizeSelect . append ( `<option value="9">9</option>` ) ;
53+ droneSizeSelect . append ( `<option value="10">10</option>` ) ;
54+ droneSizeSelect . append ( `<option value="13">13</option>` ) ;
55+ droneSizeSelect . append ( `<option value="15">15</option>` ) ;
56+ droneSizeSelect . on ( "change" , function ( ) {
57+ self . analyticsChanges [ "NornDroneSize" ] = $ ( this ) . val ( ) || null ;
58+ } ) ;
59+ }
60+
3161 // Populate Manticore models (allow None) with explicit options
3262 const manticoreSelect = $ ( "select[name='norn_manticore']" ) ;
3363 if ( manticoreSelect . length ) {
@@ -54,16 +84,24 @@ norn_config.initialize = function (callback) {
5484 }
5585
5686 // Example dropdown wiring
57- const exampleSelect = $ ( "select[name='norn_mode']" ) ;
58- if ( exampleSelect . length ) {
59- exampleSelect . on ( "change" , function ( ) {
60- const value = $ ( this ) . val ( ) ;
61- self . analyticsChanges [ "NornMode" ] = value ;
62- } ) ;
63- }
87+ // none for now
6488
65- // Example button wiring
89+ // Button wiring
6690 $ ( "a.generate" ) . on ( "click" , on_generate_handler ) ;
91+ $ ( "a.copy" ) . on ( "click" , on_copy_handler ) ;
92+ $ ( "a.save" ) . on ( "click" , on_save_handler ) ;
93+
94+ // GPS toggle wiring
95+ const gpsToggle = $ ( "#norn_gps" ) ;
96+ gpsToggle . on ( "change" , function ( ) {
97+ self . analyticsChanges [ "NornGPS" ] = $ ( this ) . is ( ":checked" ) ;
98+ } ) ;
99+
100+ // Craft name input wiring
101+ const craftNameInput = $ ( "#norn_craft_name" ) ;
102+ craftNameInput . on ( "input" , function ( ) {
103+ self . analyticsChanges [ "NornCraftName" ] = $ ( this ) . val ( ) || null ;
104+ } ) ;
67105 }
68106
69107 function on_tab_loaded_handler ( ) {
@@ -94,9 +132,13 @@ norn_config.initialize = function (callback) {
94132 }
95133
96134 function getSelectedKeys ( ) {
135+ const fcKey = $ ( "select[name='norn_fc']" ) . val ( ) || "" ;
136+ const droneSize = $ ( "select[name='norn_drone_size']" ) . val ( ) || "" ;
97137 const manticoreKey = $ ( "select[name='norn_manticore']" ) . val ( ) || "" ;
98138 const vtxKey = $ ( "select[name='norn_vtx']" ) . val ( ) || "" ;
99- return { manticoreKey, vtxKey } ;
139+ const gpsEnabled = $ ( "#norn_gps" ) . is ( ":checked" ) ;
140+ const craftName = $ ( "#norn_craft_name" ) . val ( ) || "" ;
141+ return { fcKey, droneSize, manticoreKey, vtxKey, gpsEnabled, craftName } ;
100142 }
101143
102144 function on_generate_handler ( e ) {
@@ -122,6 +164,56 @@ norn_config.initialize = function (callback) {
122164 $ ( "#norn_config_output" ) . val ( result ) ;
123165 }
124166
167+ function on_copy_handler ( e ) {
168+ e ?. preventDefault ?. ( ) ;
169+ const text = $ ( "#norn_config_output" ) . val ( ) ;
170+ if ( text ) {
171+ navigator . clipboard
172+ ?. writeText ( text )
173+ . then ( ( ) => {
174+ console . log ( "Config copied to clipboard" ) ;
175+ } )
176+ . catch ( ( err ) => {
177+ console . error ( "Failed to copy to clipboard:" , err ) ;
178+ } ) ;
179+ }
180+ }
181+
182+ function on_save_handler ( e ) {
183+ e ?. preventDefault ?. ( ) ;
184+ const text = $ ( "#norn_config_output" ) . val ( ) ;
185+ if ( ! text ) return ;
186+
187+ // Generate filename based on selected options
188+ const parts = [ ] ;
189+ const fcKey = $ ( "select[name='norn_fc']" ) . val ( ) ;
190+ const droneSize = $ ( "select[name='norn_drone_size']" ) . val ( ) ;
191+ const manticoreKey = $ ( "select[name='norn_manticore']" ) . val ( ) ;
192+ const vtxKey = $ ( "select[name='norn_vtx']" ) . val ( ) ;
193+ const gpsEnabled = $ ( "#norn_gps" ) . is ( ":checked" ) ;
194+ const craftName = $ ( "#norn_craft_name" ) . val ( ) ;
195+
196+ if ( fcKey ) parts . push ( fcKey ) ;
197+ if ( droneSize ) parts . push ( `${ droneSize } inch` ) ;
198+ if ( manticoreKey ) parts . push ( manticoreKey ) ;
199+ if ( vtxKey ) parts . push ( vtxKey ) ;
200+ if ( gpsEnabled ) parts . push ( "GPS" ) ;
201+ if ( craftName ) parts . push ( craftName ) ;
202+
203+ const filename = parts . length > 0 ? `norn_config_${ parts . join ( "_" ) } .txt` : "norn_config.txt" ;
204+
205+ // Create and trigger download
206+ const blob = new Blob ( [ text ] , { type : "text/plain" } ) ;
207+ const url = URL . createObjectURL ( blob ) ;
208+ const a = document . createElement ( "a" ) ;
209+ a . href = url ;
210+ a . download = filename ;
211+ document . body . appendChild ( a ) ;
212+ a . click ( ) ;
213+ document . body . removeChild ( a ) ;
214+ URL . revokeObjectURL ( url ) ;
215+ }
216+
125217 load_configuration_from_fc ( ) ;
126218} ;
127219
0 commit comments