Electron Adventures: Episode 88: Svelte CSV Viewer
Tags: javascript, electron, svelte, d3
Published: 2021-11-06 22:10:55

I want to experiment with a few interesting OS integrations, but first let's get an app for that - a simple CSV viewer is Svelte.

When you start the app, it will open a file dialog and ask you to select one or more CSV files to open. Then it will open a new window for each selected file.

I picked [a few CSV example files with Taylor Swift lyrics from Kaggle](https://www.kaggle.com/thespacefreak/taylor-swift-song-lyrics-all-albums) and put them into `samples` subfolder.

I'll be using `d3-dsv` to parse CSV files. It's part of the D3, but it can be used separately, so `npm install d3-dsv`.

### `index.js`

First, we need to popup the dialog to select the CSV files.

```javascript
let { app, BrowserWindow, dialog } = require("electron")

async function createWindow() {
  let { canceled, filePaths } = await dialog.showOpenDialog({
    properties: ["openFile", "multiSelections", "showHiddenFiles"],
    filters: [
      { name: "CSV files", extensions: ["csv"] },
      { name: "All Files", extensions: ["*"] }
    ],
    message: "Select a CSV file to open",
    defaultPath: `${__dirname}/samples`,
  })
  if (canceled) {
    app.quit()
  }
  for (let path of filePaths) {
    let qs = new URLSearchParams({ path }).toString();
    let win = new BrowserWindow({
      width: 1024,
      height: 768,
      webPreferences: {
        preload: `${__dirname}/preload.js`,
      },
    })
    win.loadURL(`http://localhost:5000/?${qs}`)
  }
}

app.on("ready", createWindow)

app.on("window-all-closed", () => {
  app.quit()
})
```

As I mentioned before, this is Electron API weirdness. File dialogs are really a frontend concern, so logically they should go to the frontend process, but Electron API moves them to the backend, so we have to jump through a few hoops here.

For a small change from the hex editor, we setup default filter to only show `*.csv` files, it can be overridden by the user through the OS-specific filter dropdown.

### `preload.js`

```javascript
let fs = require("fs")
let { contextBridge } = require("electron")

let q = new URLSearchParams(window.location.search)

let path = q.get("path")
let data = fs.readFileSync(path, "utf8")

contextBridge.exposeInMainWorld(
  "api", { path, data }
)
```

A small change from the hex editor, we want to return a `String`, not a `Buffer`, so we assume CSV is in UTF8. If it's not, well, it's 21st century, you should have fixed that already.

### `src/App.svelte`

And now for a very simple CSV viewer - we just dump everything into a simple HTML table, with no special handling of headers, or data types, or anything.

```html
<script>
  import {csvParseRows} from "d3-dsv"

  let data = csvParseRows(window.api.data)
  let fileName = window.api.path.split("/").slice(-1)[0]
  let title = `CSV Viewer - ${fileName}`
</script>

<h1>CSV Viewer</h1>

<table>
  {#each data as row}
    <tr>
      {#each row as column}
        <td>{column}</td>
      {/each}
    </tr>
  {/each}
</table>


<svelte:head>
  <title>{title}</title>
</svelte:head>

<style>
:global(body) {
  margin: 0;
  background-color: #444;
  color: #fff;
}
tr {
  text-align: center;
}
</style>
```

### Results

Here's the results:

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

Now that we have a base app, we'll be adding a few interesting enhancements to it over the next few episodes.

As usual, [all the code for the episode is here](https://github.com/taw/electron-adventures/tree/master/episode-88-svelte-csv-viewer).