電子冒険:エピソード37:ファイルマネージャコマンドパレット


そして今私たちのファイルマネージャにコマンドパレットを追加する時間です.これは非常に簡単に最初にされますが、我々は次のいくつかのエピソードにそれに機能を追加し続けることができます.
ファイルマネージャがまだ何もしていないので、私は物事を後方にしているのだろうかls . 最終的にすべての機能を追加します.
このエピソードはエピソード36で去ったところから始まり、エピソード35に基づいてコマンドパレット機能を追加します.
src/commands.jsこのファイルは、キーボードハンドラとコマンドパレットの間で共有されます.アプリケーションメニューを追加すると、うまくいけばそれを使用する必要があります.
export default [
  {key: "F2", action: ["app", "openPalette"]},
  {name: "Close Palette", key: "Escape", action: ["app", "closePalette"] },
  {name: "Enter Directory", key: "Enter", action: ["activePanel", "activateItem"]},
  {name: "Flip Selection", key: " ", action: ["activePanel", "flipItem"]},
  {name: "Go to First File", key: "Home", action: ["activePanel", "firstItem"]},
  {name: "Go to Last File", key: "End", action: ["activePanel", "lastItem"]},
  {name: "Go to Next File", key: "ArrowDown", action: ["activePanel", "nextItem"]},
  {name: "Go to Previous File", key: "ArrowUp", action: ["activePanel", "previousItem"]},
  {name: "Page Down", key: "PageDown", action: ["activePanel", "pageDown"]},
  {name: "Page Up", key: "PageUp", action: ["activePanel", "pageUp"]},
  {name: "Quit", key: "F10", action: ["app", "quit"]},
  {name: "Switch Panel", key: "Tab", action: ["app", "switchPanel"]},
]
アイデアは、我々はちょうどキーボードのショートカットをしたくないコマンドですkey (現在は何もないが、たくさんある).そして、我々がコマンドパレットで欲しくないコマンドは、ちょうどありませんname 現在Open Palette それがすでに開いている間、それを開くことは意味がないので.
これまでのところ、システムは余分な引数を必要としないコマンドのみを備えています.いくつかの時点で、より複雑なコマンドに拡張する必要があります.
src/Keyboard.svelte我々は、ちょうど2つの速い変化をする必要があります.コンポーネントが取得されますactive prop、そしてもしfalse , すべてのキーイベントを無視します.
私もe.stopPropagation() 現在、私たちは複数のキーボードハンドラを持っています-パレットが閉じられたときのためのこれ、そして、開いているとき、パレットの1つ.我々は、この行を必要としないが、それは我々のアプリは、より複雑になるようにいくつかのデバッグ頭痛を保存します.
残りは前と同じだ.
<script>
  import commands from "./commands.js"
  import { getContext } from "svelte"

  export let active

  let { eventBus } = getContext("app")

  function handleKey(e) {
    if (!active) {
      return
    }
    for (let command of commands) {
      if (command.key === e.key) {
        e.preventDefault()
        e.stopPropagation()
        eventBus.emit(...command.action)
      }
    }
  }

<svelte:window on:keydown={handleKey} />
</script>
src/CommandPaletteEntry.svelteこのコンポーネントは単一の利用可能なコマンドを表します.以前に電話しましたCommand , しかし、私はこれが素晴らしい名前であると思いません.
それはエピソード35からのものと同じように機能します、しかし、スタイリングは我々のアプリに沿ってより多くです、そして、スペースキーを"Space" , JSでさえ、それはちょうどです" " .
<script>
  import { getContext } from "svelte"
  let { eventBus } = getContext("app")

  export let name
  export let key
  export let action

  function handleClick() {
    eventBus.emit("app", "closePalette")
    eventBus.emit(...action)
  }
  function keyName(key) {
    if (key === " ") {
      return "Space"
    } else {
      return key
    }
  }
</script>

<li on:click={handleClick}>
  <span class="name">{name}</span>
  {#if key}
    <span class="key">{keyName(key)}</span>
  {/if}
</li>

<style>
  li {
    display: flex;
    padding: 0px 8px;
  }
  li:first-child {
    background-color: #66b;
  }
  .name {
    flex: 1;
  }
  .key {
    display: inline-block;
    background-color: hsl(180,100%,30%);
    padding: 2px;
    border: 1px solid  hsl(180,100%,20%);
    border-radius: 20%;
  }
</style>
src/CommandPalette.svelteこのコンポーネントは単純なコマンドパレットを表します.以前のものと比較して、スタイリングは、アプリと一致するように変更され、コマンド一覧からインポートされますcommands.js ここで複製される代わりに.
私たちも行う必要がありますevent.stopPropagation() はい.そうでなければ我々はプレスEnter コマンドを選択するにはEnter 通常のキーボードハンドラーにも送られます.
一般に、いくつかのデバッグを保存するために、それが必要でないときでも、イベントの伝播を止めるのは役に立ちます.
<script>
  import commands from "./commands.js"
  import { getContext } from "svelte"
  import CommandPaletteEntry from "./CommandPaletteEntry.svelte"

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

  $: matchingCommands = commands.filter(({name}) => checkMatch(pattern, name))

  function handleKey(event) {
    let {key} = event;

    if (key === "Enter") {
      event.preventDefault()
      event.stopPropagation()
      eventBus.emit("app", "closePalette")
      if (matchingCommands[0]) {
        eventBus.emit(...matchingCommands[0].action)
      }
    }
    if (key === "Escape") {
      event.preventDefault()
      event.stopPropagation()
      eventBus.emit("app", "closePalette")
    }
  }
  function checkMatch(pattern, name) {
    if (!name) {
      return false
    }
    let parts = pattern.toLowerCase().replace(/[^a-z0-9]/, "")
    let rx = new RegExp(parts.split("").join(".*"))
    name = name.toLowerCase().replace(/[^a-z0-9]/, "")
    return rx.test(name)
  }
  function focus(el) {
    el.focus()
  }
</script>

<div class="palette">
  <input use:focus bind:value={pattern} placeholder="Search for command" on:keydown={handleKey}>
  <ul>
    {#each matchingCommands as command}
      <CommandPaletteEntry {...command} />
    {/each}
  </ul>
</div>

<style>
  .palette {
    position: fixed;
    left: 0;
    top: 0;
    right: 0;
    margin: auto;
    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;
  }

  ul {
    list-style: none;
    padding: 0;
    margin: 0;
    margin-top: 8px;
  }
</style>
src/App.svelte主なアプリケーションコンポーネントはわずかに変更されました.テンプレートはCommandPalette パスactive へのフラグKeyboard コンポーネント.
<div class="ui">
  <header>
    File Manager
  </header>
  <Panel initialDirectory={initialDirectoryLeft} id="left" />
  <Panel initialDirectory={initialDirectoryRight} id="right" />
  <Footer />
</div>

<Keyboard active={!paletteOpen} />

{#if paletteOpen}
  <CommandPalette />
{/if}
スクリプトで小さなロジックを追加してパレットを開きます.
  import CommandPalette from "./CommandPalette.svelte"

  let paletteOpen = false

  function openPalette() {
    paletteOpen = true
  }
  function closePalette() {
    paletteOpen = false
  }

  eventBus.handle("app", {switchPanel, activatePanel, quit, openPalette, closePalette})
残りは前と同じだ.

結果
結果を以下に示します.

ごく最近のエピソードはかなり重かった.次のいくつかの時間は、小さな機能に焦点を当て、はるかに軽くなります.次のエピソードでは、コマンドパレットのマッチにいくつかのハイライト表示のフィードバックを追加します.
いつものように.all the code for the episode is here .