DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

Electron Adventures: Episode 49: Mkdir Dialog

It's time to add our first dialog - one for creating a new directory. But wait, it's not actually our first one, command palette is a dialog too.

So before we do anything let's refactor some code so it supports both dialogs - and more we'll be adding in the future.

Rename closePalette to closeDialog

First, in src/commands.js, let's replace palette context with a single closePalette command, just renamed to dialog context with a single command closeDialog:

  dialog: [
    {
      shortcuts: [{key: "Escape"}],
      action: ["app", "closeDialog"],
    }
  ],
Enter fullscreen mode Exit fullscreen mode

And let's change calls to app.closePalette() in src/CommandPalette.svelte, src/CommandPaletteEntry.svelte and src/App.svelte.

Start event chain in src/Panel.svelte

When the user presses F7, we need to bounce the event around a bit. First we need to send it to the active panel, because that's the component which knows where we're creating that directory at.

So here's another entry for src/commands.js:

    {
      name: "Create Directory",
      shortcuts: [{key: "F7"}],
      action: ["activePanel", "createDirectory"],
    },
Enter fullscreen mode Exit fullscreen mode

And here's its handler in src/Panel.svelte:

  function createDirectory() {
    app.openMkdirDialog(directory)
  }
Enter fullscreen mode Exit fullscreen mode

We don't need to do anything special here, just add current directory to the event and pass it along to the app.

Continue event chain in src/App.svelte

The App component used to have paletteOpen boolean flag. We need to replace it with dialog object.

Here's the relevant functions:

  let dialog = null

  $: {
    keyboardMode = "default"
    if (dialog) keyboardMode = "dialog"
    if (preview) keyboardMode = "preview"
  }
  function openPalette() {
    dialog = {type: "palette"}
  }
  function openMkdirDialog(base) {
    dialog = {type: "mkdir", base}
  }
  function closeDialog() {
    dialog = null
  }
Enter fullscreen mode Exit fullscreen mode

And we also need to add it to the template:

{#if preview}
  <Preview {...preview} />
{/if}

<div class="ui">
  <header>
    File Manager
  </header>
  <Panel initialDirectory={initialDirectoryLeft} id="left" />
  <Panel initialDirectory={initialDirectoryRight} id="right" />
  <Footer />
</div>

<Keyboard mode={keyboardMode} />

{#if dialog}
  {#if dialog.type === "palette"}
    <CommandPalette />
  {:else if dialog.type === "mkdir"}
    <MkdirDialog base={dialog.base} />
  {/if}
{/if}
Enter fullscreen mode Exit fullscreen mode

CommandPalette, MkdirDialog, and future dialogs we'll be adding share a lot of functionality, so perhaps there should be a Dialog component which includes them, and sets them in a proper place.

src/MkdirDialog.svelte

We just need a simple dialog with one input and the usual OK/Cancel buttons. One thing we've learned from the time when Orthodox File Managers were first created is that "OK" buttons should never actually say "OK", they should describe the actual action.

<form on:submit|preventDefault={submit}>
  <label>
    <div>Enter directory name:</div>
    <input use:focus bind:value={dir} placeholder="directory">
  </label>
  <div class="buttons">
    <button type="submit">Create directory</button>
    <button on:click={app.closeDialog}>Cancel</button>
  </div>
</form>
Enter fullscreen mode Exit fullscreen mode

Styling is very close to what CommandPalette and Footer already do:

<style>
  form {
    position: fixed;
    left: 0;
    top: 0;
    right: 0;
    margin: auto;
    padding: 8px;
    max-width: 50vw;
    background: #338;
    box-shadow: 0px 0px 24px #004;
  }

  input {
    font-family: inherit;
    background-color: inherit;
    font-size: inherit;
    font-weight: inherit;
    box-sizing: border-box;
    width: 100%;
    margin: 0;
    background: #66b;
    color: inherit;
  }

  input::placeholder {
    color: inherit;
    font-style: italic;
  }

  .feedback {
    font-style: italic;
  }

  .buttons {
    display: flex;
    flex-direction: row-reverse;
    margin-top: 8px;
    gap: 8px;
  }

  button {
    font-family: inherit;
    font-size: inherit;
    background-color: #66b;
    color: inherit;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

The dialog does very little - beyond some boilerplate it just needs a submit handler. Then if user typed anything, we create a new directory relative to the current directory of the active panel. If user typed nothing, we just close.

<script>
  export let base

  import path from "path-browserify"
  import { getContext } from "svelte"

  let { eventBus } = getContext("app")
  let dir = ""

  let app = eventBus.target("app")

  function submit() {
    app.closeDialog()
    if (dir !== "") {
      let target = path.join(base, dir)
      window.api.createDirectory(target)
    }
  }
  function focus(el) {
    el.focus()
  }
</script>
Enter fullscreen mode Exit fullscreen mode

To create new directory we need to add function to preload.js

preload.js

It used to be a huge pain to create directories in JavaScript, but node finally added {recursive: true} which makes it simple enough:

let createDirectory = (dir) => {
  fs.mkdirSync(dir, {recursive: true})
}
Enter fullscreen mode Exit fullscreen mode

Result

Here's the results:

Alt Text

It opens a dialog, lets user type a name, and then creates the directory. So what's missing?

  • any kind of error handling - if user tries to create directory in place they have no access to, or operating system returns any other kind of error, we don't let them know in any way
  • any kind of instant feedback - and really, we can predict what would happen. If user tries to create /etc/hack we could give live feedback below the input saying that /etc is read only for them, and such things. This is a wishlist item, which we'll likely not get to in this series, but a polished program would at least give it a try to cover more common scenarios. "It didn't work" messages should be a fallback, not a regular occurrence.
  • once we create the directory, it's not actually displayed in the active panel, as it doesn't refresh unless you navigate somewhere

In the next episode, we'll try to deal with that last problem, and refresh panels when necessary, as well as add a manual refresh command.

As usual, all the code for the episode is here.

Discussion (0)