Implementing a Command Palette and Task Timer (part 1)
John P. Rouillard
Posted on February 5, 2023
I am a developer for the open source Roundup Issue Tracker. 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
Evaluating a Command Palette
I decided to try to install a command palette. 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. 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 is a React component and spotlight is a Laravel component. I wanted one that would work with Vanilla JavaScript. I identified two candidates:
- command-pal - "The hackable command palette for the web, inspired by Visual Studio Code."
- Ninja Keys - "Keyboard shortcut interface for your website that works with Vanilla JS, Vue, and React."
Ninja Keys uses Lit while command-pal uses Svelte. 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. 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. I also downloaded the dark theme 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:
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.
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. 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 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.
Posted on February 5, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 6, 2024