macOS application menu

Standard menus (App: hide/quit; Edit: clipboard roles enabling Cmd+C/V/X/Z;
View: Refresh Cmd+R + fullscreen; Window: minimize/zoom/cycle) plus File with
New/Close Tab (Cmd+T/W), New Folder/File, and a Connect submenu built from
saved connections (folders as nested submenus, Connection Manager entry).
Custom actions forward to the webview as menuAction messages; the menu rebuilds
whenever connections change. macOS-only per electrobun support.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jokin
2026-07-11 19:17:25 +02:00
co-authored by Claude Fable 5
parent 20e5703d29
commit 2d7e7b44cd
6 changed files with 165 additions and 5 deletions
+88
View File
@@ -0,0 +1,88 @@
import { ApplicationMenu, type ApplicationMenuItemConfig } from "electrobun/bun";
import type { SavedConnection } from "../shared/types";
import type { ConnectionStore } from "./store/connections";
/**
* macOS application menu (electrobun menus are macOS-only). Standard roles plus
* app actions; custom actions are forwarded to the webview as menuAction
* messages. Rebuilt whenever saved connections change so the Connect submenu
* stays current.
*/
export async function applyApplicationMenu(store: ConnectionStore): Promise<void> {
if (process.platform !== "darwin") return;
const { folders, connections } = await store.load();
const item = (c: SavedConnection): ApplicationMenuItemConfig => ({
label: c.name,
action: `connect:${c.id}`,
});
const rootItems = connections.filter((c) => !c.folderId).map(item);
const folderMenus: ApplicationMenuItemConfig[] = folders.map((folder) => ({
label: folder.name,
submenu: connections.filter((c) => c.folderId === folder.id).map(item),
}));
const connectSubmenu: ApplicationMenuItemConfig[] = [
...rootItems,
...folderMenus,
...(rootItems.length || folderMenus.length ? [{ type: "separator" } as const] : []),
{ label: "Connection Manager…", action: "manage-connections" },
];
ApplicationMenu.setApplicationMenu([
{
submenu: [
{ role: "hide" },
{ role: "hideOthers" },
{ role: "showAll" },
{ type: "separator" },
{ label: "Quit", role: "quit" },
],
},
{
label: "File",
submenu: [
{ label: "New Tab", action: "new-tab", accelerator: "t" },
{ label: "Close Tab", action: "close-tab", accelerator: "w" },
{ type: "separator" },
{ label: "New Folder", action: "new-folder" },
{ label: "New File", action: "new-file" },
{ type: "separator" },
{ label: "Connect", submenu: connectSubmenu },
],
},
{
label: "Edit",
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
{ role: "pasteAndMatchStyle" },
{ role: "delete" },
{ role: "selectAll" },
],
},
{
label: "View",
submenu: [
{ label: "Refresh", action: "refresh-active", accelerator: "r" },
{ type: "separator" },
{ role: "toggleFullScreen" },
],
},
{
label: "Window",
submenu: [
{ role: "minimize" },
{ role: "zoom" },
{ type: "separator" },
{ role: "cycleThroughWindows" },
{ role: "bringAllToFront" },
],
},
]);
console.log(`[menu] applied (${connections.length} connections)`);
}