esp32tool 1.6.6 → 1.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/js/hex-editor.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ESP32Tool Flash Hex Editor
3
- *
3
+ *
4
4
  * A full-screen hex editor for viewing and editing flash memory content.
5
5
  * Features:
6
6
  * - Dual-pane display: HEX (left) + ASCII (right)
@@ -26,10 +26,10 @@ export class HexEditor {
26
26
  this.rowHeight = 20;
27
27
  this.selectedOffset = -1;
28
28
  this.editingPane = null; // 'hex' or 'ascii'
29
- this.editBuffer = ''; // partial hex nibble during hex editing
29
+ this.editBuffer = ""; // partial hex nibble during hex editing
30
30
 
31
31
  // Search state
32
- this.searchMatches = []; // array of byte offsets
32
+ this.searchMatches = []; // array of byte offsets
33
33
  this.currentMatchIdx = -1;
34
34
  this._searchMatchLength = 0;
35
35
  this._searchAbort = null; // AbortController for cancelling in-progress search
@@ -82,12 +82,12 @@ export class HexEditor {
82
82
  this.currentMatchIdx = -1;
83
83
  this.selectedOffset = 0;
84
84
  this.editingPane = null;
85
- this.editBuffer = '';
85
+ this.editBuffer = "";
86
86
 
87
87
  this._buildUI();
88
- this.container.classList.remove('hidden');
89
- document.body.classList.add('hexeditor-active');
90
- document.addEventListener('keydown', this._boundHandleKeyDown);
88
+ this.container.classList.remove("hidden");
89
+ document.body.classList.add("hexeditor-active");
90
+ document.addEventListener("keydown", this._boundHandleKeyDown);
91
91
 
92
92
  this._calculateLayout();
93
93
  this._render();
@@ -104,9 +104,9 @@ export class HexEditor {
104
104
 
105
105
  /** Close hex editor */
106
106
  close() {
107
- this.container.classList.add('hidden');
108
- document.body.classList.remove('hexeditor-active');
109
- document.removeEventListener('keydown', this._boundHandleKeyDown);
107
+ this.container.classList.add("hidden");
108
+ document.body.classList.remove("hexeditor-active");
109
+ document.removeEventListener("keydown", this._boundHandleKeyDown);
110
110
  if (this._resizeObserver) {
111
111
  this._resizeObserver.disconnect();
112
112
  this._resizeObserver = null;
@@ -115,23 +115,23 @@ export class HexEditor {
115
115
  this._searchAbort.abort();
116
116
  this._searchAbort = null;
117
117
  }
118
- this.container.innerHTML = '';
118
+ this.container.innerHTML = "";
119
119
  if (this.onClose) this.onClose();
120
120
  }
121
121
 
122
122
  /** Show loading overlay */
123
123
  showProgress(text, percent) {
124
124
  if (this._progressOverlay) {
125
- this._progressOverlay.classList.remove('hidden');
125
+ this._progressOverlay.classList.remove("hidden");
126
126
  this._progressText.textContent = text;
127
- this._progressBarInner.style.width = percent + '%';
127
+ this._progressBarInner.style.width = percent + "%";
128
128
  }
129
129
  }
130
130
 
131
131
  /** Hide loading overlay */
132
132
  hideProgress() {
133
133
  if (this._progressOverlay) {
134
- this._progressOverlay.classList.add('hidden');
134
+ this._progressOverlay.classList.add("hidden");
135
135
  }
136
136
  }
137
137
 
@@ -162,18 +162,19 @@ export class HexEditor {
162
162
  </div>
163
163
  </div>
164
164
  `;
165
- this._progressOverlay = this.container.querySelector('#hexedProgress');
166
- this._progressText = this.container.querySelector('#hexedProgressText');
167
- this._progressBarInner = this.container.querySelector('#hexedProgressBar');
165
+ this._progressOverlay = this.container.querySelector("#hexedProgress");
166
+ this._progressText = this.container.querySelector("#hexedProgressText");
167
+ this._progressBarInner = this.container.querySelector("#hexedProgressBar");
168
168
  }
169
169
 
170
170
  _buildUI() {
171
171
  const totalSize = this.data ? this.data.length : 0;
172
- const sizeStr = totalSize >= 1024 * 1024
173
- ? (totalSize / (1024 * 1024)).toFixed(1) + ' MB'
174
- : totalSize >= 1024
175
- ? (totalSize / 1024).toFixed(1) + ' KB'
176
- : totalSize + ' B';
172
+ const sizeStr =
173
+ totalSize >= 1024 * 1024
174
+ ? (totalSize / (1024 * 1024)).toFixed(1) + " MB"
175
+ : totalSize >= 1024
176
+ ? (totalSize / 1024).toFixed(1) + " KB"
177
+ : totalSize + " B";
177
178
 
178
179
  this.container.innerHTML = `
179
180
  <div class="hexeditor-toolbar">
@@ -222,47 +223,58 @@ export class HexEditor {
222
223
  `;
223
224
 
224
225
  // Cache DOM references
225
- this._viewport = this.container.querySelector('#hexedViewport');
226
- this._scrollContent = this.container.querySelector('#hexedScrollContent');
227
- this._statusOffset = this.container.querySelector('#hexedStatusOffset');
228
- this._statusValue = this.container.querySelector('#hexedStatusValue');
229
- this._statusModified = this.container.querySelector('#hexedStatusModified');
230
- this._searchInput = this.container.querySelector('#hexedSearch');
231
- this._searchMode = this.container.querySelector('#hexedSearchMode');
232
- this._searchInfo = this.container.querySelector('#hexedSearchInfo');
233
- this._gotoInput = this.container.querySelector('#hexedGoto');
234
- this._progressOverlay = this.container.querySelector('#hexedProgress');
235
- this._progressText = this.container.querySelector('#hexedProgressText');
236
- this._progressBarInner = this.container.querySelector('#hexedProgressBar');
237
- this._butWrite = this.container.querySelector('#hexedWrite');
226
+ this._viewport = this.container.querySelector("#hexedViewport");
227
+ this._scrollContent = this.container.querySelector("#hexedScrollContent");
228
+ this._statusOffset = this.container.querySelector("#hexedStatusOffset");
229
+ this._statusValue = this.container.querySelector("#hexedStatusValue");
230
+ this._statusModified = this.container.querySelector("#hexedStatusModified");
231
+ this._searchInput = this.container.querySelector("#hexedSearch");
232
+ this._searchMode = this.container.querySelector("#hexedSearchMode");
233
+ this._searchInfo = this.container.querySelector("#hexedSearchInfo");
234
+ this._gotoInput = this.container.querySelector("#hexedGoto");
235
+ this._progressOverlay = this.container.querySelector("#hexedProgress");
236
+ this._progressText = this.container.querySelector("#hexedProgressText");
237
+ this._progressBarInner = this.container.querySelector("#hexedProgressBar");
238
+ this._butWrite = this.container.querySelector("#hexedWrite");
238
239
 
239
240
  // Event listeners
240
- this.container.querySelector('#hexedClose').addEventListener('click', () => {
241
- if (this.hasModifications()) {
242
- if (!confirm('You have unsaved modifications. Close anyway?')) return;
243
- }
244
- this.close();
245
- });
246
-
247
- this._butWrite.addEventListener('click', () => this._handleWrite());
248
-
249
- this.container.querySelector('#hexedUndoAll').addEventListener('click', () => {
250
- if (this.modifiedOffsets.size === 0) return;
251
- if (!confirm(`Undo all ${this.modifiedOffsets.size} modifications?`)) return;
252
- for (const offset of this.modifiedOffsets) {
253
- this.data[offset] = this.originalData[offset];
254
- }
255
- this.modifiedOffsets.clear();
256
- this._render();
257
- this._updateStatus();
258
- });
259
-
260
- this.container.querySelector('#hexedSearchBtn').addEventListener('click', () => this._doSearch());
261
- this.container.querySelector('#hexedSearchPrev').addEventListener('click', () => this._navigateSearch(-1));
262
- this.container.querySelector('#hexedSearchNext').addEventListener('click', () => this._navigateSearch(1));
263
-
264
- this._searchInput.addEventListener('keydown', (e) => {
265
- if (e.key === 'Enter') {
241
+ this.container
242
+ .querySelector("#hexedClose")
243
+ .addEventListener("click", () => {
244
+ if (this.hasModifications()) {
245
+ if (!confirm("You have unsaved modifications. Close anyway?")) return;
246
+ }
247
+ this.close();
248
+ });
249
+
250
+ this._butWrite.addEventListener("click", () => this._handleWrite());
251
+
252
+ this.container
253
+ .querySelector("#hexedUndoAll")
254
+ .addEventListener("click", () => {
255
+ if (this.modifiedOffsets.size === 0) return;
256
+ if (!confirm(`Undo all ${this.modifiedOffsets.size} modifications?`))
257
+ return;
258
+ for (const offset of this.modifiedOffsets) {
259
+ this.data[offset] = this.originalData[offset];
260
+ }
261
+ this.modifiedOffsets.clear();
262
+ this._render();
263
+ this._updateStatus();
264
+ });
265
+
266
+ this.container
267
+ .querySelector("#hexedSearchBtn")
268
+ .addEventListener("click", () => this._doSearch());
269
+ this.container
270
+ .querySelector("#hexedSearchPrev")
271
+ .addEventListener("click", () => this._navigateSearch(-1));
272
+ this.container
273
+ .querySelector("#hexedSearchNext")
274
+ .addEventListener("click", () => this._navigateSearch(1));
275
+
276
+ this._searchInput.addEventListener("keydown", (e) => {
277
+ if (e.key === "Enter") {
266
278
  e.preventDefault();
267
279
  if (this.searchMatches.length > 0) {
268
280
  this._navigateSearch(1);
@@ -273,23 +285,30 @@ export class HexEditor {
273
285
  });
274
286
 
275
287
  // Clear search on input change
276
- this._searchInput.addEventListener('input', () => {
288
+ this._searchInput.addEventListener("input", () => {
277
289
  this.searchMatches = [];
278
290
  this.currentMatchIdx = -1;
279
- this._searchInfo.textContent = '';
291
+ this._searchInfo.textContent = "";
280
292
  this._render();
281
293
  });
282
294
 
283
- this.container.querySelector('#hexedGotoBtn').addEventListener('click', () => this._doGoto());
284
- this._gotoInput.addEventListener('keydown', (e) => {
285
- if (e.key === 'Enter') { e.preventDefault(); this._doGoto(); }
295
+ this.container
296
+ .querySelector("#hexedGotoBtn")
297
+ .addEventListener("click", () => this._doGoto());
298
+ this._gotoInput.addEventListener("keydown", (e) => {
299
+ if (e.key === "Enter") {
300
+ e.preventDefault();
301
+ this._doGoto();
302
+ }
286
303
  });
287
304
 
288
305
  // Virtual scroll
289
- this._viewport.addEventListener('scroll', () => this._onScroll());
306
+ this._viewport.addEventListener("scroll", () => this._onScroll());
290
307
 
291
308
  // Click handler for cells
292
- this._scrollContent.addEventListener('mousedown', (e) => this._handleCellClick(e));
309
+ this._scrollContent.addEventListener("mousedown", (e) =>
310
+ this._handleCellClick(e),
311
+ );
293
312
  }
294
313
 
295
314
  // ──────────────────── Layout & Virtual Scroll ────────────────────
@@ -300,8 +319,8 @@ export class HexEditor {
300
319
  const totalHeight = this._totalRows * this.rowHeight;
301
320
 
302
321
  // Set scroll height
303
- this._scrollContent.style.height = totalHeight + 'px';
304
- this._scrollContent.style.position = 'relative';
322
+ this._scrollContent.style.height = totalHeight + "px";
323
+ this._scrollContent.style.position = "relative";
305
324
 
306
325
  // Calculate visible rows
307
326
  const vpHeight = this._viewport.clientHeight;
@@ -320,7 +339,8 @@ export class HexEditor {
320
339
  _scrollToOffset(byteOffset) {
321
340
  const row = Math.floor(byteOffset / this.bytesPerRow);
322
341
  const vpHeight = this._viewport.clientHeight;
323
- const targetScroll = row * this.rowHeight - vpHeight / 2 + this.rowHeight / 2;
342
+ const targetScroll =
343
+ row * this.rowHeight - vpHeight / 2 + this.rowHeight / 2;
324
344
  this._viewport.scrollTop = Math.max(0, targetScroll);
325
345
  this._visibleStart = Math.floor(this._viewport.scrollTop / this.rowHeight);
326
346
  this._render();
@@ -338,11 +358,11 @@ export class HexEditor {
338
358
  const fragment = document.createDocumentFragment();
339
359
 
340
360
  // Container for positioned rows
341
- const wrapper = document.createElement('div');
342
- wrapper.style.position = 'absolute';
343
- wrapper.style.top = (start * this.rowHeight) + 'px';
344
- wrapper.style.left = '0';
345
- wrapper.style.right = '0';
361
+ const wrapper = document.createElement("div");
362
+ wrapper.style.position = "absolute";
363
+ wrapper.style.top = start * this.rowHeight + "px";
364
+ wrapper.style.left = "0";
365
+ wrapper.style.right = "0";
346
366
 
347
367
  for (let row = start; row < end; row++) {
348
368
  const rowEl = this._createRow(row);
@@ -351,39 +371,45 @@ export class HexEditor {
351
371
 
352
372
  // Replace content (keep scroll height div)
353
373
  // Remove old rendered wrapper if present
354
- const oldWrapper = this._scrollContent.querySelector('.hex-rows-wrapper');
374
+ const oldWrapper = this._scrollContent.querySelector(".hex-rows-wrapper");
355
375
  if (oldWrapper) oldWrapper.remove();
356
- wrapper.className = 'hex-rows-wrapper';
376
+ wrapper.className = "hex-rows-wrapper";
357
377
  this._scrollContent.appendChild(wrapper);
358
378
  }
359
379
 
360
380
  _createRow(rowIndex) {
361
- const row = document.createElement('div');
362
- row.className = 'hexeditor-row';
381
+ const row = document.createElement("div");
382
+ row.className = "hexeditor-row";
363
383
  const byteStart = rowIndex * this.bytesPerRow;
364
384
 
365
385
  // Highlight row if selected offset is in this row
366
- if (this.selectedOffset >= byteStart && this.selectedOffset < byteStart + this.bytesPerRow) {
367
- row.classList.add('highlight-row');
386
+ if (
387
+ this.selectedOffset >= byteStart &&
388
+ this.selectedOffset < byteStart + this.bytesPerRow
389
+ ) {
390
+ row.classList.add("highlight-row");
368
391
  }
369
392
 
370
393
  // Address
371
- const addr = document.createElement('span');
372
- addr.className = 'hexeditor-addr';
373
- addr.textContent = (this.baseAddress + byteStart).toString(16).toUpperCase().padStart(8, '0');
394
+ const addr = document.createElement("span");
395
+ addr.className = "hexeditor-addr";
396
+ addr.textContent = (this.baseAddress + byteStart)
397
+ .toString(16)
398
+ .toUpperCase()
399
+ .padStart(8, "0");
374
400
  row.appendChild(addr);
375
401
 
376
402
  // Hex cells
377
- const hexDiv = document.createElement('span');
378
- hexDiv.className = 'hexeditor-hex';
403
+ const hexDiv = document.createElement("span");
404
+ hexDiv.className = "hexeditor-hex";
379
405
 
380
406
  // Separator
381
- const sep = document.createElement('span');
382
- sep.className = 'hexeditor-sep';
407
+ const sep = document.createElement("span");
408
+ sep.className = "hexeditor-sep";
383
409
 
384
410
  // ASCII cells
385
- const asciiDiv = document.createElement('span');
386
- asciiDiv.className = 'hexeditor-ascii';
411
+ const asciiDiv = document.createElement("span");
412
+ asciiDiv.className = "hexeditor-ascii";
387
413
 
388
414
  const bytesInRow = Math.min(this.bytesPerRow, this.data.length - byteStart);
389
415
 
@@ -391,58 +417,58 @@ export class HexEditor {
391
417
  const offset = byteStart + i;
392
418
 
393
419
  // Hex cell
394
- const hexCell = document.createElement('span');
395
- hexCell.className = 'hex-cell';
420
+ const hexCell = document.createElement("span");
421
+ hexCell.className = "hex-cell";
396
422
  hexCell.dataset.offset = offset;
397
- hexCell.dataset.pane = 'hex';
423
+ hexCell.dataset.pane = "hex";
398
424
 
399
425
  // ASCII cell
400
- const asciiCell = document.createElement('span');
401
- asciiCell.className = 'ascii-cell';
426
+ const asciiCell = document.createElement("span");
427
+ asciiCell.className = "ascii-cell";
402
428
  asciiCell.dataset.offset = offset;
403
- asciiCell.dataset.pane = 'ascii';
429
+ asciiCell.dataset.pane = "ascii";
404
430
 
405
431
  if (i < bytesInRow) {
406
432
  const byte = this.data[offset];
407
- const hexStr = byte.toString(16).toUpperCase().padStart(2, '0');
433
+ const hexStr = byte.toString(16).toUpperCase().padStart(2, "0");
408
434
  hexCell.textContent = hexStr;
409
435
 
410
436
  // Color classes
411
- if (byte === 0x00) hexCell.classList.add('zero');
412
- else if (byte === 0xFF) hexCell.classList.add('ff');
437
+ if (byte === 0x00) hexCell.classList.add("zero");
438
+ else if (byte === 0xff) hexCell.classList.add("ff");
413
439
 
414
440
  // ASCII char
415
- if (byte >= 0x20 && byte <= 0x7E) {
441
+ if (byte >= 0x20 && byte <= 0x7e) {
416
442
  asciiCell.textContent = String.fromCharCode(byte);
417
443
  } else {
418
- asciiCell.textContent = '·';
419
- asciiCell.classList.add('non-printable');
444
+ asciiCell.textContent = "·";
445
+ asciiCell.classList.add("non-printable");
420
446
  }
421
447
 
422
448
  // Modified?
423
449
  if (this.modifiedOffsets.has(offset)) {
424
- hexCell.classList.add('modified');
425
- asciiCell.classList.add('modified');
450
+ hexCell.classList.add("modified");
451
+ asciiCell.classList.add("modified");
426
452
  }
427
453
 
428
454
  // Selected?
429
455
  if (offset === this.selectedOffset) {
430
- hexCell.classList.add('selected');
431
- asciiCell.classList.add('selected');
456
+ hexCell.classList.add("selected");
457
+ asciiCell.classList.add("selected");
432
458
  }
433
459
 
434
460
  // Search match?
435
461
  if (this._isSearchMatch(offset)) {
436
- hexCell.classList.add('search-match');
437
- asciiCell.classList.add('search-match');
462
+ hexCell.classList.add("search-match");
463
+ asciiCell.classList.add("search-match");
438
464
  }
439
465
  if (this._isCurrentSearchMatch(offset)) {
440
- hexCell.classList.add('search-current');
441
- asciiCell.classList.add('search-current');
466
+ hexCell.classList.add("search-current");
467
+ asciiCell.classList.add("search-current");
442
468
  }
443
469
  } else {
444
- hexCell.textContent = ' ';
445
- asciiCell.textContent = ' ';
470
+ hexCell.textContent = " ";
471
+ asciiCell.textContent = " ";
446
472
  }
447
473
 
448
474
  hexDiv.appendChild(hexCell);
@@ -458,7 +484,7 @@ export class HexEditor {
458
484
  // ──────────────────── Selection & Editing ────────────────────
459
485
 
460
486
  _handleCellClick(e) {
461
- const cell = e.target.closest('[data-offset]');
487
+ const cell = e.target.closest("[data-offset]");
462
488
  if (!cell) return;
463
489
 
464
490
  const offset = parseInt(cell.dataset.offset);
@@ -466,7 +492,7 @@ export class HexEditor {
466
492
 
467
493
  this.selectedOffset = offset;
468
494
  this.editingPane = cell.dataset.pane;
469
- this.editBuffer = '';
495
+ this.editBuffer = "";
470
496
  this._render();
471
497
  this._updateStatus();
472
498
 
@@ -476,64 +502,68 @@ export class HexEditor {
476
502
 
477
503
  _handleKeyDown(e) {
478
504
  // Only handle when hex editor is visible
479
- if (this.container.classList.contains('hidden')) return;
505
+ if (this.container.classList.contains("hidden")) return;
480
506
 
481
507
  // Don't intercept when focus is in search/goto inputs
482
- if (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT') return;
508
+ if (e.target.tagName === "INPUT" || e.target.tagName === "SELECT") return;
483
509
 
484
510
  const offset = this.selectedOffset;
485
511
  if (offset < 0 || !this.data) return;
486
512
 
487
513
  // Navigation keys
488
514
  switch (e.key) {
489
- case 'ArrowRight':
515
+ case "ArrowRight":
490
516
  e.preventDefault();
491
517
  this._selectOffset(Math.min(offset + 1, this.data.length - 1));
492
518
  return;
493
- case 'ArrowLeft':
519
+ case "ArrowLeft":
494
520
  e.preventDefault();
495
521
  this._selectOffset(Math.max(offset - 1, 0));
496
522
  return;
497
- case 'ArrowDown':
523
+ case "ArrowDown":
498
524
  e.preventDefault();
499
- this._selectOffset(Math.min(offset + this.bytesPerRow, this.data.length - 1));
525
+ this._selectOffset(
526
+ Math.min(offset + this.bytesPerRow, this.data.length - 1),
527
+ );
500
528
  return;
501
- case 'ArrowUp':
529
+ case "ArrowUp":
502
530
  e.preventDefault();
503
531
  this._selectOffset(Math.max(offset - this.bytesPerRow, 0));
504
532
  return;
505
- case 'PageDown':
533
+ case "PageDown":
506
534
  e.preventDefault();
507
- this._selectOffset(Math.min(offset + this.bytesPerRow * 16, this.data.length - 1));
535
+ this._selectOffset(
536
+ Math.min(offset + this.bytesPerRow * 16, this.data.length - 1),
537
+ );
508
538
  return;
509
- case 'PageUp':
539
+ case "PageUp":
510
540
  e.preventDefault();
511
541
  this._selectOffset(Math.max(offset - this.bytesPerRow * 16, 0));
512
542
  return;
513
- case 'Home':
543
+ case "Home":
514
544
  if (e.ctrlKey || e.metaKey) {
515
545
  e.preventDefault();
516
546
  this._selectOffset(0);
517
547
  }
518
548
  return;
519
- case 'End':
549
+ case "End":
520
550
  if (e.ctrlKey || e.metaKey) {
521
551
  e.preventDefault();
522
552
  this._selectOffset(this.data.length - 1);
523
553
  }
524
554
  return;
525
- case 'Tab':
555
+ case "Tab":
526
556
  e.preventDefault();
527
- this.editingPane = this.editingPane === 'hex' ? 'ascii' : 'hex';
528
- this.editBuffer = '';
557
+ this.editingPane = this.editingPane === "hex" ? "ascii" : "hex";
558
+ this.editBuffer = "";
529
559
  this._render();
530
560
  return;
531
561
  }
532
562
 
533
563
  // Editing
534
- if (this.editingPane === 'hex') {
564
+ if (this.editingPane === "hex") {
535
565
  this._handleHexEdit(e);
536
- } else if (this.editingPane === 'ascii') {
566
+ } else if (this.editingPane === "ascii") {
537
567
  this._handleAsciiEdit(e);
538
568
  }
539
569
  }
@@ -547,9 +577,11 @@ export class HexEditor {
547
577
  if (this.editBuffer.length === 2) {
548
578
  const newByte = parseInt(this.editBuffer, 16);
549
579
  this._setByte(this.selectedOffset, newByte);
550
- this.editBuffer = '';
580
+ this.editBuffer = "";
551
581
  // Move to next byte
552
- this._selectOffset(Math.min(this.selectedOffset + 1, this.data.length - 1));
582
+ this._selectOffset(
583
+ Math.min(this.selectedOffset + 1, this.data.length - 1),
584
+ );
553
585
  } else {
554
586
  // Show partial edit feedback – re-render to update status
555
587
  this._updateStatus();
@@ -561,7 +593,7 @@ export class HexEditor {
561
593
  e.preventDefault();
562
594
 
563
595
  const newByte = e.key.charCodeAt(0);
564
- if (newByte < 0x20 || newByte > 0x7E) return; // only printable ASCII
596
+ if (newByte < 0x20 || newByte > 0x7e) return; // only printable ASCII
565
597
 
566
598
  this._setByte(this.selectedOffset, newByte);
567
599
  this._selectOffset(Math.min(this.selectedOffset + 1, this.data.length - 1));
@@ -569,7 +601,8 @@ export class HexEditor {
569
601
 
570
602
  _setByte(offset, value) {
571
603
  if (offset < 0 || offset >= this.data.length) return;
572
- if (this.data[offset] === value && !this.modifiedOffsets.has(offset)) return;
604
+ if (this.data[offset] === value && !this.modifiedOffsets.has(offset))
605
+ return;
573
606
 
574
607
  this.data[offset] = value;
575
608
 
@@ -586,7 +619,7 @@ export class HexEditor {
586
619
 
587
620
  _selectOffset(offset) {
588
621
  this.selectedOffset = offset;
589
- this.editBuffer = '';
622
+ this.editBuffer = "";
590
623
 
591
624
  // Ensure visible
592
625
  const row = Math.floor(offset / this.bytesPerRow);
@@ -613,10 +646,14 @@ export class HexEditor {
613
646
  const mode = this._searchMode.value;
614
647
  let searchBytes;
615
648
 
616
- if (mode === 'hex') {
649
+ if (mode === "hex") {
617
650
  // Parse hex string: allow spaces, commas
618
- const cleaned = query.replace(/[,\s]/g, '');
619
- if (!/^[0-9a-fA-F]*$/.test(cleaned) || cleaned.length === 0 || cleaned.length % 2 !== 0) {
651
+ const cleaned = query.replace(/[,\s]/g, "");
652
+ if (
653
+ !/^[0-9a-fA-F]*$/.test(cleaned) ||
654
+ cleaned.length === 0 ||
655
+ cleaned.length % 2 !== 0
656
+ ) {
620
657
  this._searchInfo.textContent = 'Invalid hex (e.g. "48 65 6C 6C 6F")';
621
658
  return;
622
659
  }
@@ -643,7 +680,7 @@ export class HexEditor {
643
680
 
644
681
  this.searchMatches = [];
645
682
  this.currentMatchIdx = -1;
646
- this._searchInfo.textContent = 'Searching...';
683
+ this._searchInfo.textContent = "Searching...";
647
684
  this._render();
648
685
 
649
686
  // Non-blocking chunked search to keep UI responsive
@@ -655,7 +692,7 @@ export class HexEditor {
655
692
 
656
693
  if (this.searchMatches.length > 0) {
657
694
  // Jump to first match at or after current selection
658
- let idx = this.searchMatches.findIndex(m => m >= this.selectedOffset);
695
+ let idx = this.searchMatches.findIndex((m) => m >= this.selectedOffset);
659
696
  if (idx === -1) idx = 0;
660
697
  this.currentMatchIdx = idx;
661
698
  this._searchInfo.textContent = `${idx + 1} / ${this.searchMatches.length} matches`;
@@ -663,7 +700,7 @@ export class HexEditor {
663
700
  this._scrollToOffset(this.selectedOffset);
664
701
  } else {
665
702
  this.currentMatchIdx = -1;
666
- this._searchInfo.textContent = 'No matches found';
703
+ this._searchInfo.textContent = "No matches found";
667
704
  }
668
705
 
669
706
  this._render();
@@ -692,7 +729,10 @@ export class HexEditor {
692
729
  }
693
730
 
694
731
  const processChunk = () => {
695
- if (signal.aborted) { resolve([]); return; }
732
+ if (signal.aborted) {
733
+ resolve([]);
734
+ return;
735
+ }
696
736
 
697
737
  const end = Math.min(pos + CHUNK, len);
698
738
  while (pos <= end) {
@@ -724,8 +764,10 @@ export class HexEditor {
724
764
  if (this.searchMatches.length === 0) return;
725
765
 
726
766
  this.currentMatchIdx += direction;
727
- if (this.currentMatchIdx >= this.searchMatches.length) this.currentMatchIdx = 0;
728
- if (this.currentMatchIdx < 0) this.currentMatchIdx = this.searchMatches.length - 1;
767
+ if (this.currentMatchIdx >= this.searchMatches.length)
768
+ this.currentMatchIdx = 0;
769
+ if (this.currentMatchIdx < 0)
770
+ this.currentMatchIdx = this.searchMatches.length - 1;
729
771
 
730
772
  this.selectedOffset = this.searchMatches[this.currentMatchIdx];
731
773
  this._searchInfo.textContent = `${this.currentMatchIdx + 1} / ${this.searchMatches.length} matches`;
@@ -737,11 +779,15 @@ export class HexEditor {
737
779
  _isSearchMatch(offset) {
738
780
  if (this.searchMatches.length === 0) return false;
739
781
  const len = this._searchMatchLength || 1;
740
- return this.searchMatches.some(m => offset >= m && offset < m + len);
782
+ return this.searchMatches.some((m) => offset >= m && offset < m + len);
741
783
  }
742
784
 
743
785
  _isCurrentSearchMatch(offset) {
744
- if (this.currentMatchIdx < 0 || this.currentMatchIdx >= this.searchMatches.length) return false;
786
+ if (
787
+ this.currentMatchIdx < 0 ||
788
+ this.currentMatchIdx >= this.searchMatches.length
789
+ )
790
+ return false;
745
791
  const m = this.searchMatches[this.currentMatchIdx];
746
792
  const len = this._searchMatchLength || 1;
747
793
  return offset >= m && offset < m + len;
@@ -750,15 +796,17 @@ export class HexEditor {
750
796
  // ──────────────────── Go To Address ────────────────────
751
797
 
752
798
  _doGoto() {
753
- const val = this._gotoInput.value.trim().replace(/^0x/i, '');
799
+ const val = this._gotoInput.value.trim().replace(/^0x/i, "");
754
800
  const addr = parseInt(val, 16);
755
801
  if (isNaN(addr)) return;
756
802
 
757
803
  // Convert absolute address to offset
758
804
  const offset = addr >= this.baseAddress ? addr - this.baseAddress : addr;
759
805
  if (offset < 0 || offset >= this.data.length) {
760
- this._gotoInput.style.borderColor = '#c62828';
761
- setTimeout(() => { this._gotoInput.style.borderColor = ''; }, 1000);
806
+ this._gotoInput.style.borderColor = "#c62828";
807
+ setTimeout(() => {
808
+ this._gotoInput.style.borderColor = "";
809
+ }, 1000);
762
810
  return;
763
811
  }
764
812
 
@@ -773,28 +821,29 @@ export class HexEditor {
773
821
  const off = this.selectedOffset;
774
822
  const absAddr = this.baseAddress + off;
775
823
  const byte = this.data[off];
776
- const hexStr = byte.toString(16).toUpperCase().padStart(2, '0');
824
+ const hexStr = byte.toString(16).toUpperCase().padStart(2, "0");
777
825
  const dec = byte;
778
- const bin = byte.toString(2).padStart(8, '0');
779
- const chr = (byte >= 0x20 && byte <= 0x7E) ? `'${String.fromCharCode(byte)}'` : '-';
826
+ const bin = byte.toString(2).padStart(8, "0");
827
+ const chr =
828
+ byte >= 0x20 && byte <= 0x7e ? `'${String.fromCharCode(byte)}'` : "-";
829
+
830
+ this._statusOffset.textContent = `Offset: 0x${absAddr.toString(16).toUpperCase().padStart(8, "0")} (${off})`;
780
831
 
781
- this._statusOffset.textContent = `Offset: 0x${absAddr.toString(16).toUpperCase().padStart(8, '0')} (${off})`;
782
-
783
832
  let valueStr = `Hex: 0x${hexStr} | Dec: ${dec} | Bin: ${bin} | Char: ${chr}`;
784
833
  if (this.editBuffer.length > 0) {
785
834
  valueStr += ` [typing: ${this.editBuffer}_]`;
786
835
  }
787
836
  this._statusValue.textContent = valueStr;
788
837
  } else {
789
- this._statusOffset.textContent = 'Offset: -';
790
- this._statusValue.textContent = 'Value: -';
838
+ this._statusOffset.textContent = "Offset: -";
839
+ this._statusValue.textContent = "Value: -";
791
840
  }
792
841
 
793
842
  if (this.modifiedOffsets.size > 0) {
794
843
  this._statusModified.textContent = `● ${this.modifiedOffsets.size} byte(s) modified`;
795
844
  this._butWrite.disabled = false;
796
845
  } else {
797
- this._statusModified.textContent = '';
846
+ this._statusModified.textContent = "";
798
847
  this._butWrite.disabled = true;
799
848
  }
800
849
  }
@@ -804,18 +853,22 @@ export class HexEditor {
804
853
  async _handleWrite() {
805
854
  if (this.modifiedOffsets.size === 0) return;
806
855
  if (!this.onWriteFlash) {
807
- alert('Write handler not configured');
856
+ alert("Write handler not configured");
808
857
  return;
809
858
  }
810
859
 
811
860
  const count = this.modifiedOffsets.size;
812
- if (!confirm(`Write ${count} modified byte(s) to flash?\n\nThis will erase and reprogram affected sectors.`)) {
861
+ if (
862
+ !confirm(
863
+ `Write ${count} modified byte(s) to flash?\n\nThis will erase and reprogram affected sectors.`,
864
+ )
865
+ ) {
813
866
  return;
814
867
  }
815
868
 
816
869
  try {
817
870
  this._butWrite.disabled = true;
818
- this.showProgress('Writing changes to flash...', 0);
871
+ this.showProgress("Writing changes to flash...", 0);
819
872
  await this.onWriteFlash(this.data, this.modifiedOffsets);
820
873
 
821
874
  // Update original snapshot after successful write
@@ -827,7 +880,7 @@ export class HexEditor {
827
880
  } catch (err) {
828
881
  this.hideProgress();
829
882
  this._butWrite.disabled = false;
830
- alert('Write failed: ' + (err.message || err));
883
+ alert("Write failed: " + (err.message || err));
831
884
  }
832
885
  }
833
886
  }