Implementing a Command Palette and Task Timer (part 1)
Tags: webdev, javascript, opensource
Published: 2023-02-05 21:09:25

I am a developer for the open source [Roundup Issue Tracker](https://www.roundup-tracker.org). It has many use cases. One is to develop issue trackers like GitHub Issues, Bugzilla, or Request Tracker. I also develop a custom issue tracker for a help desk environment. This article describes the steps in adding a task-timing feature for that tracker.

A user requested a task timer. The workflow:
  * open an issue page,
  * start a timer,
  * start the task associated with the issue,
  * use the issue page to document the work.
 
When done, the user would:
  * stop the timer,
  * finish documentation, attach files, and make other changes to the issue,
  * submit the time and other changes. 

There are a few decisions to make:
  * timer functions
     * is start/stop enough?
     * is pause/restart needed?
     * does the user need to change/set/edit the timer?
     * do we need to track seconds, minutes, and hours? Since the customer is billed for the time, recording seconds seems excessive. Is there a scenario where the timer would need to record seconds?
  * timer controls
     * do we use a button/buttons? Does checking a checkbox activate the timer?
     * what does the UI look like for each timer function?
        Are the controls in the issue page?
        Are the controls in a floating popup? Does the popup need to be movable/collapsible
	so it doesn't block access to the underlying issue?
  * notification feedback
     * how is the user notified that the timer is running/paused/stopped?
     * how does the user see the elapsed time?
  * future planning
     * how to add controls without cluttering an already complex interface
     * what impact will this have on adding future feature requests in the same context
     * does this guide us in implementing future workflows

<!--more-->
## Evaluating a Command Palette

I decided to try to install a [command palette](https://www.commandbar.com/blog/command-palette-past-present-and-future). A command palette is a UI interface usually activated by a hotkey. It allows searching and selecting from a context-sensitive list of commands. You might be familiar with the [VS Code command palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette). Command palettes have many advantages for users:

   * discover useful commands (and their shortcuts)
   * faster than scrolling down a long list of commands
   * keyboard is faster than using the mouse
   * invisible until activated
   * make commands available that wouldn't be important enough to get a button or other UI element

There are many command palette implementations in JavaScript. Some are used with specific frameworks. For example, [kbar](https://github.com/timc1/kbar) is a React component and [spotlight](https://github.com/livewire-ui/spotlight) is a Laravel component. I wanted one that would work with [Vanilla JavaScript](https://www.javatpoint.com/what-is-vanilla-javascript). I identified two candidates:

  * [command-pal](https://github.com/benwinding/command-pal) - "The hackable command palette for the web, inspired by Visual Studio Code."
  * [Ninja Keys](https://github.com/ssleptsov/ninja-keys) - "Keyboard shortcut interface for your website that works with Vanilla JS, Vue, and React."

Ninja Keys uses [Lit](https://lit.dev/) while command-pal uses [Svelte](https://svelte.dev). I don't have any experience with either, so.... Both of them can bind any command on the palette to a hotkey thanks to [hotkeys.js](https://github.com/jaywcjlove/hotkeys/). Both are MIT licensed. Command-pal is larger in size, but it also bundles all the libraries it needs. It looks like Ninja Keys loads libraries/modules on demand from CDNs on the internet. Being able to use the library without internet access is a nice feature.

Command-pal promotes itself as "hackable". This usually means flexibility and sometimes simplicity. I like both. Command-pal's feature set wasn't as impressive as Ninja Keys. But it does include a floating button to trigger the command palette on mobile. This is a nice touch as moving the page to access UI elements can be tedious on mobile.

## Adding command-pal

As a result, I chose command-pal. Integrating it was easy. I downloaded the file from the [CDN](https://cdn.jsdelivr.net/npm/command-pal). I also downloaded the [dark theme](https://github.com/benwinding/command-pal/tree/master/public) from GitHub. I added a script tag and stylesheet link to the top-level page Roundup template file. This makes command-pal available on all the tracker's pages.

To invoke it, I added:

```js
 const c = new CommandPal({
   hotkey: "ctrl+space",
   hotkeysGlobal: true,
   commands: commands,
 });
 c.start();
```
inside a `script` tag. The tracker is a web application. Sadly, the classic command palette hotkeys: "ctrl+k" or "ctrl+shift+p" are already used by the browser. I also wanted to activate the palette using the hotkey when focused on an input, select, or textarea. `hotkeysGlobal: true` should do that, but it didn't work for me in Firefox, Chrome, or Brave). I [submitted a pull request to fix it.](https://github.com/benwinding/command-pal/pull/18)


The commands array included:


```
  [
    {
      name: "Exit Command Palette",
      contexts: [ "all" ],
      weight: -10,
    },
    {
      name: "Initial Screen",
      description: "Screen shown after login.",
      contexts: [ "all" ],
      handler: () => (window.location.href = "."),
      shortcut: "ctrl+i",_
      weight: 5,
    }, ...
]
```
Besides the fields used by command-pal:

  * name,
  * description,
  * handler,
  * shortcut

I added extra fields:

  * weight
  * contexts

These were inspired by the [Superhuman blog on building a remarkable command palette](https://blog.superhuman.com/how-to-build-a-remarkable-command-palette/). Among the things they suggest are:

   * order commands by popularity/utility
   * listed commands are context sensitive

The commands are initially sorted by weight (using commands.sort()). This displays the most popular (highest weight) commands at the top of the menu. Displaying the [search results sorted by weight](https://github.com/benwinding/command-pal/pull/30#issuecomment-1418245061) is an ongoing project.

The command is shown if its `contexts` property matches the current context. For example, the task timing commands only make sense when editing an issue. They should not be shown when viewing a list of issues, or a user's profile page. Inspecting the page's URL determines the current context. The code filters the command list, eliminating commands that are not appropriate for the context. Only then is CommandPal invoked.

I hope you have enjoyed learning about command palettes and command-pal in particular. In part 2, we will use command-pal to control the task timer and look at how it integrates with the tracker built using the Roundup Issue Tracker.
{% embed https://www.roundup-tracker.org %}