extractify.zip/components/binary-viewer.vue

145 lines
3.3 KiB
Vue
Raw Normal View History

2024-11-04 09:12:02 +03:00
<script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'
import { FilesManager } from '~/composables/files-manager';
import { iFile } from '~/composables/worker/7zip-manager';
interface Props {
file: iFile,
filesManager: FilesManager
}
const { file, filesManager } = defineProps<Props>()
const buffer = ref<Uint8Array>()
const containerRef = ref<HTMLElement>()
const isLoading = ref(true)
const rowHeight = 24 // pixels per row
const visibleRows = ref(0)
const scrollTop = ref(0)
// Calculate total rows and visible window
const totalRows = computed(() => buffer.value ? Math.ceil(buffer.value.length / 16) : 0)
const startRow = computed(() => Math.floor(scrollTop.value / rowHeight))
const endRow = computed(() => Math.min(startRow.value + visibleRows.value + 5, totalRows.value))
// Get only the visible rows data
const visibleData = computed(() => {
if (!buffer.value) return []
const rows = []
for (let i = startRow.value; i < endRow.value; i++) {
const offset = i * 16
const chunk = buffer.value.slice(offset, offset + 16)
const hex = Array.from(chunk).map(b => b.toString(16).padStart(2, '0')).join(' ')
const ascii = Array.from(chunk).map(b => b >= 32 && b <= 126 ? String.fromCharCode(b) : '.').join('')
rows.push({
address: offset.toString(16).padStart(8, '0'),
hex: hex.padEnd(48, ' '),
ascii
})
}
return rows
})
// Handle scroll events
function onScroll(event: Event) {
const container = event.target as HTMLElement
scrollTop.value = container.scrollTop
}
// Calculate visible rows based on container height
function updateVisibleRows() {
if (containerRef.value) {
visibleRows.value = Math.ceil(containerRef.value.clientHeight / rowHeight)
}
}
onMounted(async () => {
try {
buffer.value = await filesManager.getFileContent(file.path, "binary") as Uint8Array
updateVisibleRows()
window.addEventListener('resize', updateVisibleRows)
} finally {
isLoading.value = false
}
})
</script>
<template>
<div class="binary-viewer">
<div v-if="isLoading" class="loading">
Loading...
</div>
<div
v-else
ref="containerRef"
class="hex-container"
@scroll="onScroll"
>
<div
class="scroll-content"
:style="{
height: `${totalRows * rowHeight}px`,
paddingTop: `${startRow * rowHeight}px`
}"
>
<div
v-for="row in visibleData"
:key="row.address"
class="hex-line"
>
<span class="address">{{ row.address }}</span>
<span class="hex">{{ row.hex }}</span>
<span class="ascii">{{ row.ascii }}</span>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.binary-viewer {
font-family: monospace;
white-space: pre;
padding: 1rem;
background-color: var(--v-theme-surface);
height: calc(100vh - 150px);
}
.hex-container {
height: 100%;
overflow-y: auto;
position: relative;
}
.scroll-content {
position: absolute;
width: 100%;
}
.hex-line {
line-height: 1.5;
height: v-bind(rowHeight + 'px');
}
.address {
color: #666;
margin-right: 1rem;
}
.hex {
margin-right: 1rem;
}
.ascii {
color: #666;
}
.loading {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
</style>