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"],
}
],
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"],
},
And here's its handler in src/Panel.svelte
:
function createDirectory() {
app.openMkdirDialog(directory)
}
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
}
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}
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>
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>
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>
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})
}
Result
Here's the results:
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.
Top comments (0)