@@ -148,6 +148,7 @@ export class NVSEditor {
148148 // ─────── Integrity checking and statistics (from Berry nvs.be) ───────
149149
150150 getStatistics ( ) {
151+ const MAX_ENTRY_COUNT = 126 ;
151152 const stats = {
152153 pages_total : this . pages . length ,
153154 pages_active : 0 ,
@@ -166,8 +167,6 @@ export class NVSEditor {
166167 } ;
167168
168169 for ( const page of this . pages ) {
169- stats . pages_total ++ ;
170-
171170 if ( page . state === 'ACTIVE' ) stats . pages_active ++ ;
172171 else if ( page . state === 'FULL' ) stats . pages_full ++ ;
173172 else if ( page . state === 'UNINIT' ) stats . pages_empty ++ ;
@@ -180,20 +179,20 @@ export class NVSEditor {
180179 stats . pages_bad_header_crc ++ ;
181180 }
182181
182+ // Iterate ALL slots in the page bitmap to count erased/empty/written
183+ const stateBitmap = this . data . slice ( page . offset + 32 , page . offset + 64 ) ;
184+ for ( let slotIndex = 0 ; slotIndex < MAX_ENTRY_COUNT ; slotIndex ++ ) {
185+ const slotState = this . _getNVSItemState ( stateBitmap , slotIndex ) ;
186+ if ( slotState === 0 ) stats . entries_erased ++ ;
187+ else if ( slotState === 1 ) stats . entries_empty ++ ;
188+ else if ( slotState === 2 ) stats . entries_written ++ ;
189+ }
190+
191+ // Check entry header / data CRC for parsed (WRITTEN) items
183192 for ( const item of page . items ) {
184- const itemState = this . _getNVSItemState ( this . data . slice ( page . offset + 32 , page . offset + 64 ) ,
185- ( item . offset - page . offset - 64 ) / 32 ) ;
186-
187- if ( itemState === 2 ) stats . entries_written ++ ;
188- else if ( itemState === 0 ) stats . entries_erased ++ ;
189- else stats . entries_empty ++ ;
190-
191- // Check entry header CRC
192193 if ( ! item . headerCrcValid ) {
193194 stats . entries_bad_header_crc ++ ;
194195 }
195-
196- // Check data CRC for string/blob types
197196 if ( item . dataCrcValid !== undefined && ! item . dataCrcValid ) {
198197 stats . entries_bad_data_crc ++ ;
199198 }
@@ -203,18 +202,6 @@ export class NVSEditor {
203202 return stats ;
204203 }
205204
206- getNamespaces ( ) {
207- const namespaces = new Map ( ) ;
208- for ( const page of this . pages ) {
209- for ( const item of page . items ) {
210- if ( item . nsIndex === 0 && item . namespace ) {
211- namespaces . set ( item . value , item . namespace ) ;
212- }
213- }
214- }
215- return namespaces ;
216- }
217-
218205 // ─────── Blob integrity checking (from Berry nvs.be) ───────
219206
220207 getBlobs ( ) {
@@ -263,7 +250,7 @@ export class NVSEditor {
263250
264251 // For inline blob (small blobs stored in header)
265252 if ( item . datatype === 0x42 && item . size > 0 && item . span === 1 ) {
266- const inlineOff = item . offset + 24 ;
253+ const inlineOff = item . offset + 32 ;
267254 blob . chunks . push ( {
268255 offset : inlineOff ,
269256 length : item . size ,
@@ -650,7 +637,6 @@ export class NVSEditor {
650637 </div>
651638 <button id="nvsStats" title="Show statistics and integrity report">📊 Stats</button>
652639 <button id="nvsBlobs" title="Show blob information">📦 Blobs</button>
653- <button id="nvsNamespaces" title="Show namespaces">📁 Namespaces</button>
654640 <button id="nvsRefresh" title="Re-parse data">Refresh</button>
655641 <button id="nvsWrite" class="primary" disabled>Write to Flash</button>
656642 <button id="nvsClose">Close</button>
@@ -717,34 +703,14 @@ export class NVSEditor {
717703 } ) ;
718704
719705 // Stats button
720- const statsBtn = this . container . querySelector ( '#nvsStats' ) ;
721- if ( statsBtn ) {
722- statsBtn . addEventListener ( 'click' , ( ) => {
723- this . _showStats ( ) ;
724- } ) ;
725- } else {
726- console . error ( 'Stats button not found' ) ;
727- }
706+ this . container . querySelector ( '#nvsStats' ) . addEventListener ( 'click' , ( ) => {
707+ this . _showStats ( ) ;
708+ } ) ;
728709
729710 // Blobs button
730- const blobsBtn = this . container . querySelector ( '#nvsBlobs' ) ;
731- if ( blobsBtn ) {
732- blobsBtn . addEventListener ( 'click' , ( ) => {
733- this . _showBlobs ( ) ;
734- } ) ;
735- } else {
736- console . error ( 'Blobs button not found' ) ;
737- }
738-
739- // Namespaces button
740- const nsBtn = this . container . querySelector ( '#nvsNamespaces' ) ;
741- if ( nsBtn ) {
742- nsBtn . addEventListener ( 'click' , ( ) => {
743- this . _showNamespaces ( ) ;
744- } ) ;
745- } else {
746- console . error ( 'Namespaces button not found' ) ;
747- }
711+ this . container . querySelector ( '#nvsBlobs' ) . addEventListener ( 'click' , ( ) => {
712+ this . _showBlobs ( ) ;
713+ } ) ;
748714
749715 this . _renderContent ( ) ;
750716 }
@@ -1118,7 +1084,32 @@ export class NVSEditor {
11181084 const blob = blobs . get ( key ) ;
11191085 const blobData = this . getBlobData ( blob ) ;
11201086 const hexDump = NVSEditor . hexDump ( blobData ) ;
1121- alert ( `Hex dump for ${ key } :\n\n${ hexDump } ` ) ;
1087+
1088+ const dialogBody = dialogContainer . querySelector ( '.nvs-dialog-body' ) ;
1089+ if ( ! dialogBody ) return ;
1090+
1091+ // Reuse a single hex-dump pane per key inside this dialog
1092+ const paneId = `nvs-hex-dump-${ key } ` ;
1093+ let pre = dialogBody . querySelector ( `#${ CSS . escape ( paneId ) } ` ) ;
1094+ if ( ! pre ) {
1095+ pre = document . createElement ( 'pre' ) ;
1096+ pre . id = paneId ;
1097+ pre . className = 'nvs-hex-dump' ;
1098+ pre . style . fontFamily = '"SF Mono", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace' ;
1099+ pre . style . fontSize = '12px' ;
1100+ pre . style . background = '#f5f5f5' ;
1101+ pre . style . border = '1px solid #e0e0e0' ;
1102+ pre . style . borderRadius = '6px' ;
1103+ pre . style . padding = '10px' ;
1104+ pre . style . maxHeight = '400px' ;
1105+ pre . style . overflow = 'auto' ;
1106+ pre . style . whiteSpace = 'pre' ;
1107+ pre . style . margin = '8px 0 18px 0' ;
1108+ dialogBody . appendChild ( pre ) ;
1109+ }
1110+ // textContent escapes HTML by default
1111+ pre . textContent = `Hex dump for ${ key } :\n\n${ hexDump } ` ;
1112+ pre . scrollIntoView ( { behavior : 'smooth' , block : 'nearest' } ) ;
11221113 } ) ;
11231114 } ) ;
11241115
@@ -1133,49 +1124,16 @@ export class NVSEditor {
11331124 const a = document . createElement ( 'a' ) ;
11341125 a . href = url ;
11351126 a . download = `${ key } .bin` ;
1127+ a . style . display = 'none' ;
1128+ document . body . appendChild ( a ) ;
11361129 a . click ( ) ;
1137- URL . revokeObjectURL ( url ) ;
1130+ // Defer cleanup so the download has time to start before the URL is revoked
1131+ setTimeout ( ( ) => {
1132+ URL . revokeObjectURL ( url ) ;
1133+ a . remove ( ) ;
1134+ } , 0 ) ;
11381135 } ) ;
11391136 } ) ;
11401137 }
11411138
1142- _showNamespaces ( ) {
1143- const namespaces = this . getNamespaces ( ) ;
1144-
1145- let html = '' ;
1146- if ( namespaces . size > 0 ) {
1147- for ( const [ index , name ] of namespaces ) {
1148- html += `<div class="nvs-namespace-item"><strong>Index ${ index } :</strong> ${ this . _esc ( name ) } </div>` ;
1149- }
1150- } else {
1151- html = '<div class="nvs-empty">No namespace entries found.</div>' ;
1152- }
1153-
1154- const dialogHtml = `
1155- <div class="nvs-dialog-overlay" id="nvsNamespacesDialog">
1156- <div class="nvs-dialog">
1157- <div class="nvs-dialog-header">
1158- <h3>📁 Namespaces Found (${ namespaces . size } )</h3>
1159- <button class="nvs-dialog-close">×</button>
1160- </div>
1161- <div class="nvs-dialog-body">
1162- ${ html }
1163- </div>
1164- </div>
1165- </div>` ;
1166-
1167- const dialogContainer = document . createElement ( 'div' ) ;
1168- dialogContainer . innerHTML = dialogHtml ;
1169- document . body . appendChild ( dialogContainer ) ;
1170-
1171- dialogContainer . querySelector ( '.nvs-dialog-close' ) . addEventListener ( 'click' , ( ) => {
1172- dialogContainer . remove ( ) ;
1173- } ) ;
1174-
1175- dialogContainer . querySelector ( '.nvs-dialog-overlay' ) . addEventListener ( 'click' , ( e ) => {
1176- if ( e . target === dialogContainer . querySelector ( '.nvs-dialog-overlay' ) ) {
1177- dialogContainer . remove ( ) ;
1178- }
1179- } ) ;
1180- }
11811139}
0 commit comments