LaunchMenu Development

Make it. Make it now.

Learn about all the modules available in LaunchMenu:

HelloWorld.ts
export const info = { name: "HelloWorld", description: "A minimal example applet", version: "0.0.0", icon: <img width={30} src={Path.join(__dirname, "..", "images", "icon.png")} />, }; export const settings = createSettings({ version: "0.0.0", settings: () => createSettingsFolder({ ...info, children: { username: createStringSetting({name: "Username", init: "Bob"}), }}), }); const items = [ createStandardMenuItem({ name: "Hello world", onExecute: ({context}) => alert(`Hello ${context.settings.get(settings).username.get()}!`) }), ]; export default declare({ info, settings, search: async (query, hook) => ({children: searchAction.get(items)}), });

Super flexible

LaunchMenu consists of independent composable building blocks, allowing you to create any experience you desire. The interfaces are designed to be as flexible as possible without restricting utility.

Quality environment

Use common web technologies to create applets, including TypeScript and React, which increase the maintainability of your code. Given LaunchMenu is built with Electron your applets will be cross-platform and able to reach a larger audience.

Vast API

LaunchMenu contains several systems to simplify common scenarios. Applets can make use of the settings system, menus and items, context menu actions, search handlers, themed components and more.

Ease of development

Composable approach

LaunchMenu has been created as a set of independent building blocks. This allows for more reuse, and more customization. When no block exists for your exact use case, you can often find a block that's doing something similar to what you want and reuse functions, React components and other structures that it uses internally.

Developer environment

LaunchMenu is written in TypeScript and all methods are documented with TSDoc. This provides excellent intellisense support in modern IDEs (e.g. VSCode) which helps you understand what functions are doing under the hood and how to communicate with them.

LaunchMenu build tools

LaunchMenu includes a build utility, which compiles all TypeScript and resources into LaunchMenu-ready packages. A system is also provided which auto-reloads applets as you modify them to ease testing.

Applet ecosystem

Simply upload your completed applet with the keywordlaunchmenu-applet to NPM in order for it to load into the applet ecosystem.

Open source

This is still a developing project, and we value your input. We welcome all new suggestions and contributions! We have already been through several re-designs, and we're not afraid of rewriting code when better alternatives are discovered.
Item Creation
const category = createStandardCategory({ name: "Hello" }); const items = [ createStandardMenuItem({ name: "Hello world", icon: <img width={30} src={Path.join(__dirname, "..", "images", "icon.png")} />, content: <Box>Hello world!</Box>, onExecute: () => alert(`Hello john!`), }), createFolderMenuItem({ name: "Some folder", category, children: [ createStandardMenuItem({ name: "Bye world", description: "More items within this directory", content: <Box>Bye world!</Box>, onExecute: () => alert(`Bye bob`), }), ], }), ];

Menu items

Standard items

Standard factories exist for creating simple but very customizable items. It automatically supports functionality like: searching, categories, content and context-items.

Custom items

Despite standardized factories being provided, menu items can be any react component with attached functionality. Menu-item sub-components can be reused to reduce duplicate code, yet allow you to customize whatever is necessary.

Context-menu

Using the action system any additional functionality can be added to menu items.

Categories

LaunchMenu supports simple item grouping using categories, to neatly organize your menus.
Search
// Use a cache to keep the same items when typing const resultCache = new SearchCache((name: string, image: string, species: string) => createStandardMenuItem({ name, description: species, content: <img width="100%" src={image} />, onExecute: () => alert(`${name}!`), }) ); export default declare({ info, settings, search: async (query, hook) => { const rawData = await fetch( `https://rickandmortyapi.com/api/character?name=${query.search}` ); const data: { results: {name: string; image: string; species: string}[]; } = rawData.ok ? await rawData.json() : {results: []}; const items = data.results.map(({name, image, species}) => resultCache.get(name, image, species) ); return {children: searchAction.get(items)}; }, });

Search system

Asynchronous searches

Searches are always asynchronous, allowing you to fetch remote data.

Recursive searches

The search system is built for performing recursive searches, allowing you to add or remove a whole subtree of search results at once.

Dynamic searches

Using model-react any search result can be added or removed after the search was performed, dynamically updating the results.
setTaskPriority.ts
import { createContextAction, singlePromptExecuteHandler, promptSelectExecuteHandler, Priority, createStandardMenuItem } from "@launchmenu/core"; import {Field} from "model-react"; export type ITaskPriority = "high" | "medium" | "low"; export const setTaskPriority = createContextAction({ name: "Set priority level", contextItem: { priority: Priority.MEDIUM /* Not to be confused with ITaskPriority */ }, core: (fields: Field<ITaskPriority>[]) => { const executeBinding = singlePromptExecuteHandler.createBinding({ fields, valueRetriever: ({field}) => promptSelectExecuteHandler.createBinding({ field, options: ["low", "medium", "high"], createOptionView: level => createStandardMenuItem({name: level}), }), }); return { // Return the bindings for executing the action in the menu actionBindings: [executeBinding], }; }, });
Action Usage
import {ITaskPriority, createStandardMenuItem} from "@launchmenu/core"; function createTaskMenuItem({name}: {name: string}) { const level = new Field<ITaskPriority>("medium"); return createStandardMenuItem({ name, description: hook => level.get(hook), onExecute: () => alert(level.get()), actionBindings: [setTaskPriority.createBinding(level)], }); } const items = [ createTaskMenuItem({name: "Meet Alice"}), createTaskMenuItem({name: "Make pancakes"}), createTaskMenuItem({name: "Free Hat"}), ];

Action system

Powerful action system

LaunchMenu provides a generic action system. Menu items only provide functionality through their action bindings. This includes providing items to context menus, keyboard input handling, search handling, execution handling, and even selection event handling.

Multiple item selection

The action system is especially designed to support combined behavior of multiple selected actions. This makes sure users don't have to repeat the same actions multiple times and instead allows them to bulk operations.

Action specialization

Actions can easily be extended in order to specialize them. This allows one action in the context-menu to perform different - but similar - tasks depending on the selected item.

Item specialization

When an action is specialized, the context-menu item can also be specialized. This way a more specific name can be displayed as long as all selected items perform the exact same task.

Undo/redo integration

The primary execute action has built-in support for commands, allowing the user to undo and redo their performed actions.
Settings
export const settings = createSettings({ version: "0.0.0", settings: () => createSettingsFolder({ ...info, children: { username: createStringSetting({name: "Username", init: "Bob"}), }, }), }); const Content: FC<{text: string}> = ({text}) => { const context = useIOContext(); const [hook] = useDataHook(); const name = context?.settings.get(settings).username.get(hook); return ( <Box color="primary"> {text} {name}! </Box> ); }; const items = [ createStandardMenuItem({ name: "Hello world", content: <Content text="Hello" />, onExecute: ({context}) => alert(`Hello ${context.settings.get(settings).username.get()}!`), }), ];

Settings

Simple setting declarations

Easily create settings that users are able to alter to their likings, using the same menu items as used elsewhere.

Simple setting usage

Obtain the setting values using the IOContext in a variety of places. Settings can also easily be subscribed to, instantly updating whatever depends on the setting.

Custom setting types

Since settings are simply menu-items, one can easily make their own setting type.
Undo/redo
const createTimesItem = (name: string) => { const times = new Field(1); return createStandardMenuItem({ name: hook => `${name} x${times.get(hook)}`, // Use a command to allow the user to revert the change onExecute: () => new SetFieldCommand(times, times.get() + 1), }); }; const items = [createTimesItem("Hello world"), createTimesItem("Bye world")];

Undo/redo system

Integrated undo/redo system

Easily dispatch undoable commands from throughout your apple, and allow users to make mistakes without any consequences.

Asynchronous command execution

Commands are allow to execute asynchronously, and perform any complex tasks.

Channeled command execution

Since commands may execute asynchronously, "resources" can be used to define different channels for unrelated commands to execute in parallel.

Community

As mentioned before, LaunchMenu is fully open-source! We welcome any contributions to the project, especially third party applets. In case that you want to contribute to our official repository, we do recommend discussing your ideas with us first however. This prevents you from investing a lot of time into something that doesn't line up with our vision. That said, we're open to most ideas, and definitely to any discussions! So don't hesitate to join the community, both as developer or as user, at one of the following links: