From f7fbf57095037e5c5da1f747ec6fce47806979eb Mon Sep 17 00:00:00 2001 From: kerms Date: Tue, 10 Mar 2026 13:17:47 +0100 Subject: [PATCH] feat: Wireshark-style hex/field bidirectional highlighting --- .../app-image-viewer/AppImageViewer.vue | 45 ++++- components/app-image-viewer/HexDump.vue | 68 ++++++- components/app-image-viewer/HexFieldPanel.vue | 169 ++++++++++++++++++ 3 files changed, 276 insertions(+), 6 deletions(-) create mode 100644 components/app-image-viewer/HexFieldPanel.vue diff --git a/components/app-image-viewer/AppImageViewer.vue b/components/app-image-viewer/AppImageViewer.vue index a40e2a6..ae2b2b4 100644 --- a/components/app-image-viewer/AppImageViewer.vue +++ b/components/app-image-viewer/AppImageViewer.vue @@ -5,7 +5,9 @@ import { SPI_FLASH_MODE_NAMES, SPI_FLASH_SPEED_NAMES, SPI_FLASH_SIZE_NAMES, parseAppImage, } from '../../lib/app-image'; +import { computeFieldRanges, type FieldGroup } from '../../lib/app-image/ranges'; import HexDump from './HexDump.vue'; +import HexFieldPanel from './HexFieldPanel.vue'; const props = defineProps<{ isDark?: boolean; @@ -17,6 +19,9 @@ const showHex = ref(false); const statusMessage = ref(''); const statusType = ref<'success' | 'error' | 'info'>('info'); const fileName = ref(''); +const fieldGroups = ref([]); +const activeRange = ref<{ start: number; end: number } | null>(null); +const hexDumpRef = ref | null>(null); let statusTimer: ReturnType | null = null; @@ -43,6 +48,27 @@ function formatSha256(data: Uint8Array): string { return Array.from(data).map(b => b.toString(16).padStart(2, '0')).join(''); } +function onByteHover(offset: number | null) { + if (offset === null) { activeRange.value = null; return; } + // Pass 1: named field match (checked across all groups first) + for (const g of fieldGroups.value) { + const f = g.fields.find(f => offset >= f.start && offset < f.end); + if (f) { activeRange.value = { start: f.start, end: f.end }; return; } + } + // Pass 2: data-only group (segment data blobs) + for (const g of fieldGroups.value) { + if (g.fields.length === 0 && offset >= g.start && offset < g.end) { + activeRange.value = { start: g.start, end: g.end }; return; + } + } + activeRange.value = null; +} + +function onFieldSelect(range: { start: number; end: number }) { + activeRange.value = range; + hexDumpRef.value?.scrollTo(range.start); +} + async function handleOpenFile(file: File): Promise { try { const buffer = await file.arrayBuffer(); @@ -53,6 +79,8 @@ async function handleOpenFile(file: File): Promise { } imageInfo.value = parseAppImage(data); rawData.value = data; + fieldGroups.value = computeFieldRanges(data, imageInfo.value); + activeRange.value = null; showHex.value = false; fileName.value = file.name; showStatus(`已加载 ${file.name} (${data.byteLength} 字节)`, 'success'); @@ -146,7 +174,22 @@ async function handleOpenFile(file: File): Promise { {{ showHex ? '隐藏原始字节' : '查看原始字节' }} - + diff --git a/components/app-image-viewer/HexDump.vue b/components/app-image-viewer/HexDump.vue index ffec08b..2e0dece 100644 --- a/components/app-image-viewer/HexDump.vue +++ b/components/app-image-viewer/HexDump.vue @@ -1,11 +1,20 @@