diff --git a/.prettierignore b/.prettierignore index 5f86f710f..a2a277659 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,2 @@ sst-env.d.ts -desktop/src/bindings.ts +packages/desktop/src/bindings.ts diff --git a/packages/app/src/app.tsx b/packages/app/src/app.tsx index 11fdb5743..8a111472b 100644 --- a/packages/app/src/app.tsx +++ b/packages/app/src/app.tsx @@ -30,7 +30,7 @@ import { HighlightsProvider } from "@/context/highlights" import Layout from "@/pages/layout" import DirectoryLayout from "@/pages/directory-layout" import { ErrorPage } from "./pages/error" -import { Suspense } from "solid-js" +import { Suspense, JSX } from "solid-js" const Home = lazy(() => import("@/pages/home")) const Session = lazy(() => import("@/pages/session")) @@ -84,7 +84,7 @@ function ServerKey(props: ParentProps) { ) } -export function AppInterface(props: { defaultUrl?: string }) { +export function AppInterface(props: { defaultUrl?: string; children?: JSX.Element }) { const platform = usePlatform() const stored = (() => { @@ -111,7 +111,7 @@ export function AppInterface(props: { defaultUrl?: string }) { ( + root={(routerProps) => ( @@ -119,7 +119,10 @@ export function AppInterface(props: { defaultUrl?: string }) { - {props.children} + + {props.children} + {routerProps.children} + diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx index 86b4fbeb1..32e36815e 100644 --- a/packages/app/src/components/titlebar.tsx +++ b/packages/app/src/components/titlebar.tsx @@ -82,6 +82,21 @@ export function Titlebar() { navigate(to) } + command.register(() => [ + { + id: "common.goBack", + title: language.t("common.goBack"), + category: language.t("command.category.view"), + onSelect: back, + }, + { + id: "common.goForward", + title: language.t("common.goForward"), + category: language.t("command.category.view"), + onSelect: forward, + }, + ]) + const getWin = () => { if (platform.platform !== "desktop") return diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts index df3181133..c32af728a 100644 --- a/packages/app/src/index.ts +++ b/packages/app/src/index.ts @@ -1,2 +1,3 @@ export { PlatformProvider, type Platform } from "./context/platform" export { AppBaseProviders, AppInterface } from "./app" +export { useCommand } from './context/command' diff --git a/packages/desktop/src/bindings.ts b/packages/desktop/src/bindings.ts index 440e138b4..c6ca0fec7 100644 --- a/packages/desktop/src/bindings.ts +++ b/packages/desktop/src/bindings.ts @@ -1,6 +1,6 @@ // This file has been generated by Tauri Specta. Do not edit this file manually. -import { invoke as __TAURI_INVOKE, Channel } from "@tauri-apps/api/core" +import { invoke as __TAURI_INVOKE } from "@tauri-apps/api/core" /** Commands */ export const commands = { diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx index 30cb7ba7a..66e86bf52 100644 --- a/packages/desktop/src/index.tsx +++ b/packages/desktop/src/index.tsx @@ -1,7 +1,7 @@ // @refresh reload import { webviewZoom } from "./webview-zoom" import { render } from "solid-js/web" -import { AppBaseProviders, AppInterface, PlatformProvider, Platform } from "@opencode-ai/app" +import { AppBaseProviders, AppInterface, PlatformProvider, Platform, useCommand } from "@opencode-ai/app" import { open, save } from "@tauri-apps/plugin-dialog" import { getCurrent, onOpenUrl } from "@tauri-apps/plugin-deep-link" import { openPath as openerOpenPath } from "@tauri-apps/plugin-opener" @@ -18,11 +18,11 @@ import { Splash } from "@opencode-ai/ui/logo" import { createSignal, Show, Accessor, JSX, createResource, onMount, onCleanup } from "solid-js" import { UPDATER_ENABLED } from "./updater" -import { createMenu } from "./menu" import { initI18n, t } from "./i18n" import pkg from "../package.json" import "./styles.css" import { commands } from "./bindings" +import { createMenu } from "./menu" const root = document.getElementById("root") if (import.meta.env.DEV && !(root instanceof HTMLElement)) { @@ -342,7 +342,10 @@ const createPlatform = (password: Accessor): Platform => ({ webviewZoom, }) -createMenu() +let menuTrigger = null as null | ((id: string) => void) +createMenu((id) => { + menuTrigger?.(id) +}) void listenForDeepLinks() render(() => { @@ -373,7 +376,19 @@ render(() => { window.__OPENCODE__ ??= {} window.__OPENCODE__.serverPassword = data().password ?? undefined - return + function Inner() { + const cmd = useCommand() + + menuTrigger = (id) => cmd.trigger(id) + + return null + } + + return ( + + + + ) }} diff --git a/packages/desktop/src/menu.ts b/packages/desktop/src/menu.ts index d41084404..9af6d2b84 100644 --- a/packages/desktop/src/menu.ts +++ b/packages/desktop/src/menu.ts @@ -1,13 +1,14 @@ import { Menu, MenuItem, PredefinedMenuItem, Submenu } from "@tauri-apps/api/menu" import { type as ostype } from "@tauri-apps/plugin-os" import { relaunch } from "@tauri-apps/plugin-process" +import { openUrl } from "@tauri-apps/plugin-opener" import { runUpdater, UPDATER_ENABLED } from "./updater" import { installCli } from "./cli" import { initI18n, t } from "./i18n" import { commands } from "./bindings" -export async function createMenu() { +export async function createMenu(trigger: (id: string) => void) { if (ostype() !== "macos") return await initI18n() @@ -60,29 +61,25 @@ export async function createMenu() { }), ].filter(Boolean), }), - // await Submenu.new({ - // text: "File", - // items: [ - // await MenuItem.new({ - // enabled: false, - // text: "Open Project...", - // }), - // await PredefinedMenuItem.new({ - // item: "Separator" - // }), - // await MenuItem.new({ - // enabled: false, - // text: "New Session", - // }), - // await PredefinedMenuItem.new({ - // item: "Separator" - // }), - // await MenuItem.new({ - // enabled: false, - // text: "Close Project", - // }) - // ] - // }), + await Submenu.new({ + text: "File", + items: [ + await MenuItem.new({ + text: "New Session", + action: () => trigger("session.new"), + }), + await MenuItem.new({ + text: "Open Project...", + action: () => trigger("project.open"), + }), + await PredefinedMenuItem.new({ + item: "Separator", + }), + await PredefinedMenuItem.new({ + item: "CloseWindow", + }), + ], + }), await Submenu.new({ text: "Edit", items: [ @@ -109,6 +106,79 @@ export async function createMenu() { }), ], }), + await Submenu.new({ + text: "View", + items: [ + await MenuItem.new({ + action: () => trigger("sidebar.toggle"), + text: "Toggle Sidebar", + }), + await MenuItem.new({ + action: () => trigger("terminal.toggle"), + text: "Toggle Terminal", + }), + await MenuItem.new({ + action: () => trigger("fileTree.toggle"), + text: "Toggle File Tree", + }), + await PredefinedMenuItem.new({ + item: "Separator", + }), + await MenuItem.new({ + action: () => trigger("common.goBack"), + text: "Back", + }), + await MenuItem.new({ + action: () => trigger("common.goForward"), + text: "Forward", + }), + await PredefinedMenuItem.new({ + item: "Separator", + }), + await MenuItem.new({ + action: () => trigger("session.next"), + text: "Previous Session", + }), + await MenuItem.new({ + action: () => trigger("session.previous"), + text: "Next Session", + }), + await PredefinedMenuItem.new({ + item: "Separator", + }), + ], + }), + await Submenu.new({ + text: "Help", + items: [ + // missing native macos search + await MenuItem.new({ + action: () => openUrl("https://opencode.ai/docs"), + text: "OpenCode Documentation", + }), + await MenuItem.new({ + action: () => openUrl("https://discord.com/invite/opencode"), + text: "Support Forum", + }), + await PredefinedMenuItem.new({ + item: "Separator", + }), + // await MenuItem.new({ + // text: "Release Notes", + // }), + await PredefinedMenuItem.new({ + item: "Separator", + }), + await MenuItem.new({ + action: () => openUrl("https://github.com/anomalyco/opencode/issues/new?template=feature_request.yml"), + text: "Share Feedback", + }), + await MenuItem.new({ + action: () => openUrl("https://github.com/anomalyco/opencode/issues/new?template=bug_report.yml"), + text: "Report a Bug", + }), + ], + }), ], }) menu.setAsAppMenu()