Forem

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

1 1

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.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

đź‘‹ Kindness is contagious

Immerse yourself in a wealth of knowledge with this piece, supported by the inclusive DEV Community—every developer, no matter where they are in their journey, is invited to contribute to our collective wisdom.

A simple “thank you” goes a long way—express your gratitude below in the comments!

Gathering insights enriches our journey on DEV and fortifies our community ties. Did you find this article valuable? Taking a moment to thank the author can have a significant impact.

Okay