Browse Source

desktop: add more basic menu bar items

Brendan Allan 2 months ago
parent
commit
e0e32ed3a8

+ 1 - 1
.prettierignore

@@ -1,2 +1,2 @@
 sst-env.d.ts
-desktop/src/bindings.ts
+packages/desktop/src/bindings.ts

+ 7 - 4
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 }) {
         <GlobalSDKProvider>
           <GlobalSyncProvider>
             <Router
-              root={(props) => (
+              root={(routerProps) => (
                 <SettingsProvider>
                   <PermissionProvider>
                     <LayoutProvider>
@@ -119,7 +119,10 @@ export function AppInterface(props: { defaultUrl?: string }) {
                         <ModelsProvider>
                           <CommandProvider>
                             <HighlightsProvider>
-                              <Layout>{props.children}</Layout>
+                              <Layout>
+                                {props.children}
+                                {routerProps.children}
+                              </Layout>
                             </HighlightsProvider>
                           </CommandProvider>
                         </ModelsProvider>

+ 15 - 0
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
 

+ 1 - 0
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'

+ 1 - 1
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 = {

+ 19 - 4
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<string | null>): 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 <AppInterface defaultUrl={data().url} />
+            function Inner() {
+              const cmd = useCommand()
+
+              menuTrigger = (id) => cmd.trigger(id)
+
+              return null
+            }
+
+            return (
+              <AppInterface defaultUrl={data().url}>
+                <Inner />
+              </AppInterface>
+            )
           }}
         </ServerGate>
       </AppBaseProviders>

+ 94 - 24
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()