Add Windows desktop app menu (#28420)

This commit is contained in:
Luke Parker
2026-05-20 14:28:15 +10:00
committed by GitHub
parent 66d409d679
commit 82c5d45601
14 changed files with 554 additions and 134 deletions
+45 -126
View File
@@ -1,141 +1,60 @@
import { Menu, shell } from "electron"
import { BrowserWindow, Menu, shell } from "electron"
import type { MenuItemConstructorOptions } from "electron"
import { DESKTOP_MENU, desktopMenuVisible, type DesktopMenuEntry, type DesktopMenuRole } from "@opencode-ai/app/desktop-menu"
import { UPDATER_ENABLED } from "./constants"
import { createMainWindow } from "./windows"
import { runDesktopMenuAction } from "./desktop-menu-actions"
type Deps = {
trigger: (id: string) => void
checkForUpdates: () => void
reload: () => void
relaunch: () => void
}
export function createMenu(deps: Deps) {
if (process.platform !== "darwin") return
const template: Electron.MenuItemConstructorOptions[] = [
{
label: "OpenCode",
submenu: [
{ role: "about" },
{
label: "Check for Updates...",
enabled: UPDATER_ENABLED,
click: () => deps.checkForUpdates(),
},
{
label: "Settings",
accelerator: "Cmd+,",
click: () => deps.trigger("settings.open"),
},
{
label: "Reload Webview",
click: () => deps.reload(),
},
{
label: "Restart",
click: () => deps.relaunch(),
},
{ type: "separator" },
{ role: "hide" },
{ role: "hideOthers" },
{ role: "unhide" },
{ type: "separator" },
{ role: "quit" },
],
},
{
label: "File",
submenu: [
{ label: "New Session", accelerator: "Shift+Cmd+S", click: () => deps.trigger("session.new") },
{ label: "Open Project...", accelerator: "Cmd+O", click: () => deps.trigger("project.open") },
{
label: "New Window",
accelerator: "Cmd+Shift+N",
click: () => createMainWindow(),
},
{ type: "separator" },
{ role: "close" },
],
},
{
label: "Edit",
submenu: [
{ role: "undo" },
{ role: "redo" },
{ type: "separator" },
{ role: "cut" },
{ role: "copy" },
{ role: "paste" },
{ role: "selectAll" },
],
},
{
label: "View",
submenu: [
{ label: "Toggle Sidebar", accelerator: "Cmd+B", click: () => deps.trigger("sidebar.toggle") },
{ label: "Toggle Terminal", accelerator: "Ctrl+`", click: () => deps.trigger("terminal.toggle") },
{ label: "Toggle File Tree", click: () => deps.trigger("fileTree.toggle") },
{ type: "separator" },
{ role: "reload" },
{ role: "toggleDevTools" },
{ type: "separator" },
{ role: "resetZoom" },
{ role: "zoomIn" },
{ role: "zoomOut" },
{ type: "separator" },
{ role: "togglefullscreen" },
],
},
{
label: "Go",
submenu: [
{ label: "Back", accelerator: "Cmd+[", click: () => deps.trigger("common.goBack") },
{ label: "Forward", accelerator: "Cmd+]", click: () => deps.trigger("common.goForward") },
{ type: "separator" },
{
label: "Previous Session",
accelerator: "Option+Up",
click: () => deps.trigger("session.previous"),
},
{
label: "Next Session",
accelerator: "Option+Down",
click: () => deps.trigger("session.next"),
},
{ type: "separator" },
{
label: "Previous Project",
accelerator: "Cmd+Option+Up",
click: () => deps.trigger("project.previous"),
},
{
label: "Next Project",
accelerator: "Cmd+Option+Down",
click: () => deps.trigger("project.next"),
},
],
},
{ role: "windowMenu" },
{
label: "Help",
submenu: [
{ label: "OpenCode Documentation", click: () => shell.openExternal("https://opencode.ai/docs") },
{ label: "Support Forum", click: () => shell.openExternal("https://discord.com/invite/opencode") },
{ type: "separator" },
{ type: "separator" },
{
label: "Share Feedback",
click: () =>
shell.openExternal("https://github.com/anomalyco/opencode/issues/new?template=feature_request.yml"),
},
{
label: "Report a Bug",
click: () => shell.openExternal("https://github.com/anomalyco/opencode/issues/new?template=bug_report.yml"),
},
],
},
]
const template = DESKTOP_MENU.filter((menu) => desktopMenuVisible(menu, "macos")).map((menu) => {
if (menu.role) return { role: nativeRole(menu.role) }
return {
label: menu.label,
submenu: menu.items?.filter((entry) => desktopMenuVisible(entry, "macos")).map((entry) => nativeItem(entry, deps)),
}
})
Menu.setApplicationMenu(Menu.buildFromTemplate(template))
}
function nativeItem(entry: DesktopMenuEntry, deps: Deps): MenuItemConstructorOptions {
if (entry.type === "separator") return { type: "separator" }
if (entry.role) return { role: nativeRole(entry.role) }
const item: MenuItemConstructorOptions = {
label: entry.label,
accelerator: entry.accelerator?.macos,
enabled: entry.enabled === "updater" ? UPDATER_ENABLED : undefined,
}
if (entry.command) {
const command = entry.command
item.click = () => deps.trigger(command)
}
if (entry.action) {
const action = entry.action
item.click = () =>
runDesktopMenuAction(BrowserWindow.getFocusedWindow(), action, {
checkForUpdates: deps.checkForUpdates,
relaunch: deps.relaunch,
})
}
if (entry.href) {
const href = entry.href
item.click = () => shell.openExternal(href)
}
return item
}
function nativeRole(role: DesktopMenuRole) {
return role as NonNullable<MenuItemConstructorOptions["role"]>
}