import { Component, Input, ViewEncapsulation, ViewChild, ElementRef, AfterViewInit, OnDestroy, ChangeDetectorRef } from '@angular/core';
interface HexLine {
addr: string;
hexReal: string;
hexPad: string;
asciiReal: string;
asciiPad: string;
}
@Component({
selector: 'p3xr-hex-monitor',
standalone: true,
template: `
@for (line of lines; track line.addr) {
{{ line.addr }}
{{ line.hexReal }}{{ line.hexPad }}
{{ line.asciiReal }}{{ line.asciiPad }}
}
`,
encapsulation: ViewEncapsulation.None,
styles: [`:host { display: block; }`],
})
export class HexMonitorComponent implements AfterViewInit, OnDestroy {
lines: HexLine[] = [];
contentScrollWidth = 0;
@Input() truncated: boolean = false;
@ViewChild('hexContent') contentRef!: ElementRef;
@ViewChild('hexScrollbar') scrollbarRef!: ElementRef;
private _value = '';
private resizeObs: ResizeObserver | null = null;
private viewReady = false;
constructor(private cdr: ChangeDetectorRef) {}
@Input()
set value(val: string) {
this._value = val ?? '';
this.lines = HexMonitorComponent.parseHexLines(this._value);
if (this.viewReady) {
requestAnimationFrame(() => this.measure());
}
}
get value(): string { return this._value; }
ngAfterViewInit(): void {
this.viewReady = true;
this.measure();
this.resizeObs = new ResizeObserver(() => {
this.measure();
this.cdr.detectChanges();
});
if (this.contentRef?.nativeElement) {
this.resizeObs.observe(this.contentRef.nativeElement);
}
}
ngOnDestroy(): void {
this.resizeObs?.disconnect();
}
syncScroll(): void {
if (this.contentRef?.nativeElement && this.scrollbarRef?.nativeElement) {
this.contentRef.nativeElement.scrollLeft = this.scrollbarRef.nativeElement.scrollLeft;
}
}
private measure(): void {
const el = this.contentRef?.nativeElement;
if (!el) return;
this.contentScrollWidth = el.scrollWidth;
}
static parseHexLines(str: string): HexLine[] {
if (!str) return [];
const encoded = new TextEncoder().encode(str);
const lines: HexLine[] = [];
for (let i = 0; i < encoded.length; i += 16) {
const chunk = encoded.slice(i, i + 16);
const n = chunk.length;
const addr = i.toString(16).padStart(8, '0');
const padded = new Uint8Array(16);
padded.set(chunk);
const left = Array.from(padded.slice(0, 8)).map(b => b.toString(16).padStart(2, '0')).join(' ');
const right = Array.from(padded.slice(8)).map(b => b.toString(16).padStart(2, '0')).join(' ');
const full = left + ' ' + right;
if (n === 16) {
const ascii = Array.from(padded).map(b => b >= 0x20 && b <= 0x7e ? String.fromCharCode(b) : '.').join('');
lines.push({ addr, hexReal: full, hexPad: '', asciiReal: ascii, asciiPad: '' });
} else {
const splitPos = n <= 8 ? 3 * n - 1 : 25 + 3 * (n - 8) - 1;
const asciiAll = Array.from(padded).map(b => b >= 0x20 && b <= 0x7e ? String.fromCharCode(b) : '.').join('');
lines.push({
addr,
hexReal: full.substring(0, splitPos),
hexPad: full.substring(splitPos),
asciiReal: asciiAll.substring(0, n),
asciiPad: asciiAll.substring(n),
});
}
}
return lines;
}
}