Electron Adventures: Episode 62: Hex Editor Displaying Data
Tags: javascript, electron, svelte
Published: 2021-09-26 05:35:39

Hex editor is not a very complicated project, but to keep the posts manageable let's do it one step at a time.

Let's start by displaying data in the `MainView`.

### `fast-printf`

One function that most languages have but browser-side JavaScript somehow lacks is anything like `printf`.

In pretty much any other language, to get a 2 digit hex number you can do `printf("%02x", i)` or something like that.

Fortunately there's a lot of npm packages for it, but many are called "printf" but do not implement even basic printf functionality.

After a few tries with other packages I found that [`fast-printf`](https://www.npmjs.com/package/fast-printf) does everything I need.

### `src/App.svelte`

To start displaying data, first we need to generate some. And to we can just throw some numbers into an array in a loop. We'll actually want to use `Buffer` or `Uint8Array` for this eventually, but one thing at a time.

```html
<script>
  import MainView from "./MainView.svelte"
  import Decodings from "./Decodings.svelte"
  import StatusBar from "./StatusBar.svelte"

  let data = []
  let offset = 1234

  for (let i=0; i<10010; i++) {
    data.push(i & 0xFF)
  }
</script>

<div class="editor">
  <MainView {data} />
  <Decodings {data} {offset} />
  <StatusBar {offset} />
</div>

<svelte:head>
  <title>fancy-data.bin</title>
</svelte:head>
```

### `src/StatusBar.svelte`

For hex files there's situations where we want to dispaly offset as decimal, and situations where we want to display offset as hex. Since we have a lot of space on the status bar, we can do both.

`printf` from `fast-printf` package will handle the formatting.

```html
<script>
  import { printf } from "fast-printf"
  export let offset

  $: hexOffset = printf("%x", offset)
</script>

<div>
  Offset: {offset} ({hexOffset})
</div>

<style>
  div {
    margin-top: 8px;
  }
</style>
```

### `src/MainView.svelte`

Svelte doesn't have `{#while}` or `{#for}` loops, just `{#each}`, so we need to convert data to slices.

We can put the slicing in `$:` block so it happens automtically whenever `data` changes.

```html
<script>
  import Slice from "./Slice.svelte"

  export let data

  let slices

  $: {
    slices = []
    for (let i=0; i<data.length; i+=16) {
      slices.push({
        offset: i,
        data: data.slice(i, i+16),
      })
    }
  }

</script>

<div class="main">
  <table>
    {#each slices as slice}
      <Slice {...slice} />
    {/each}
  </table>
</div>

<style>
  .main {
    flex: 1 1 auto;
    overflow-y: auto;
  }
  table {
    width: 100%;
  }
</style>
```

### `src/Slice.svelte`

This component represents one row of the main view. It needs to display offset, hex data, and ascii data.

We sometimes want to display decimal and sometimes hex offset, but there's definitely no space for both. It would be nice to have some shortcut to switch between the modes.

```html
<script>
  import { printf } from "fast-printf"
  import HexGroup from "./HexGroup.svelte"
  import AsciiSlice from "./AsciiSlice.svelte"

  export let offset
  export let data
</script>

<tr>
  <td class="offset">{printf("%06d", offset)}</td>
  <HexGroup data={data.slice(0, 4)} />
  <HexGroup data={data.slice(4, 8)} />
  <HexGroup data={data.slice(8, 12)} />
  <HexGroup data={data.slice(12, 16)} />
  <AsciiSlice {data} />
</tr>

<style>
  tr:nth-child(even) {
    background-color: #555;
  }
</style>
```

### `src/HexGroup.svelte`

For now this component is very simple for now, thanks to `printf`. We'll need to modify it so it tells us which exact cell is being hovered.

```html
<script>
  import { printf } from "fast-printf"

  export let data
</script>

<td class="hex">
  <span>
    {data[0] !== undefined ? printf("%02x", data[0]) : ""}
  </span>
  <span>
    {data[1] !== undefined ? printf("%02x", data[1]) : ""}
  </span>
  <span>
    {data[2] !== undefined ? printf("%02x", data[2]) : ""}
  </span>
  <span>
    {data[3] !== undefined ? printf("%02x", data[3]) : ""}
  </span>
</td>
```

### `src/AsciiSlice.svelte`

And finally, the ASCII preview of the data of the slice.

There are three cases here:

* it's a printable ASCII character - then we print it
* it's space - then we print it as `&nbsp;` to keep data aligned
* it's anything else - then we put a gray dot instead, to keep other data aligned

An obvious question is why don't we print UTF8 characters. This is mainly because that complicate data alignment a lot. And what if start of a character is on one line, but the rest of it is on the next? Or when there are combining characters? Binary data rarely has enough complex UTF8 to justify this. And the table under the code should handle such cases well enough.

```html
<script>
  export let data
</script>

<td class="ascii">
  {#each data as d}
    {#if d >= 33 && d <= 126}
      {String.fromCharCode(d)}
    {:else if d == 32}
      &nbsp;
    {:else}
      <span class="unprintable">.</span>
    {/if}
  {/each}
</td>

<style>
  .unprintable {
    color: #aaa;
  }
</style>
```

### Result

Here's the results:

![Episode 62 Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8a783mlhapkv5ek6widr.png)

In the next episode, we'll make data decoding table work.

As usual, [all the code for the episode is here](https://github.com/taw/electron-adventures/tree/master/episode-62-hex-editor-displaying-data).