summaryrefslogtreecommitdiffhomepage
path: root/packages/desktop/src
diff options
context:
space:
mode:
authorAdam <[email protected]>2025-12-22 20:01:16 -0600
committerAdam <[email protected]>2025-12-22 20:01:25 -0600
commit81c3c638953fb7730a6965b3614d57e3c4919dde (patch)
treebea87a6b98078d52dd037da16500b29a28587edc /packages/desktop/src
parentb76bd4141dd78db94ab485743b4dc21d918cd85e (diff)
downloadopencode-81c3c638953fb7730a6965b3614d57e3c4919dde.tar.gz
opencode-81c3c638953fb7730a6965b3614d57e3c4919dde.zip
chore: rename packages/tauri -> packages/desktop
Diffstat (limited to 'packages/desktop/src')
-rw-r--r--packages/desktop/src/index.tsx114
-rw-r--r--packages/desktop/src/menu.ts94
-rw-r--r--packages/desktop/src/updater.ts45
3 files changed, 253 insertions, 0 deletions
diff --git a/packages/desktop/src/index.tsx b/packages/desktop/src/index.tsx
new file mode 100644
index 000000000..57c1fbe55
--- /dev/null
+++ b/packages/desktop/src/index.tsx
@@ -0,0 +1,114 @@
+// @refresh reload
+import { render } from "solid-js/web"
+import { App, PlatformProvider, Platform } from "@opencode-ai/app"
+import { open, save } from "@tauri-apps/plugin-dialog"
+import { open as shellOpen } from "@tauri-apps/plugin-shell"
+import { type as ostype } from "@tauri-apps/plugin-os"
+import { AsyncStorage } from "@solid-primitives/storage"
+import { fetch as tauriFetch } from "@tauri-apps/plugin-http"
+import { Store } from "@tauri-apps/plugin-store"
+
+import { UPDATER_ENABLED } from "./updater"
+import { createMenu } from "./menu"
+import { check, Update } from "@tauri-apps/plugin-updater"
+import { invoke } from "@tauri-apps/api/core"
+import { relaunch } from "@tauri-apps/plugin-process"
+
+const root = document.getElementById("root")
+if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
+ throw new Error(
+ "Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
+ )
+}
+
+let update: Update | null = null
+
+const platform: Platform = {
+ platform: "tauri",
+
+ async openDirectoryPickerDialog(opts) {
+ const result = await open({
+ directory: true,
+ multiple: opts?.multiple ?? false,
+ title: opts?.title ?? "Choose a folder",
+ })
+ return result
+ },
+
+ async openFilePickerDialog(opts) {
+ const result = await open({
+ directory: false,
+ multiple: opts?.multiple ?? false,
+ title: opts?.title ?? "Choose a file",
+ })
+ return result
+ },
+
+ async saveFilePickerDialog(opts) {
+ const result = await save({
+ title: opts?.title ?? "Save file",
+ defaultPath: opts?.defaultPath,
+ })
+ return result
+ },
+
+ openLink(url: string) {
+ shellOpen(url)
+ },
+
+ storage: (name = "default.dat") => {
+ const api: AsyncStorage = {
+ _store: null,
+ _getStore: async () => api._store || (api._store = Store.load(name)),
+ getItem: async (key: string) => (await (await api._getStore()).get(key)) ?? null,
+ setItem: async (key: string, value: string) => await (await api._getStore()).set(key, value),
+ removeItem: async (key: string) => await (await api._getStore()).delete(key),
+ clear: async () => await (await api._getStore()).clear(),
+ key: async (index: number) => (await (await api._getStore()).keys())[index],
+ getLength: async () => (await api._getStore()).length(),
+ get length() {
+ return api.getLength()
+ },
+ }
+ return api
+ },
+
+ checkUpdate: async () => {
+ if (!UPDATER_ENABLED) return { updateAvailable: false }
+ update = await check()
+ if (!update) return { updateAvailable: false }
+ await update.download()
+ return { updateAvailable: true, version: update.version }
+ },
+
+ update: async () => {
+ if (!UPDATER_ENABLED || !update) return
+ await update.install()
+ },
+
+ restart: async () => {
+ await invoke("kill_sidecar")
+ await relaunch()
+ },
+
+ // @ts-expect-error
+ fetch: tauriFetch,
+}
+
+createMenu()
+
+// Stops mousewheel events from reaching Tauri's pinch-to-zoom handler
+root?.addEventListener("mousewheel", (e) => {
+ e.stopPropagation()
+})
+
+render(() => {
+ return (
+ <PlatformProvider value={platform}>
+ {ostype() === "macos" && (
+ <div class="bg-background-base border-b border-border-weak-base h-8" data-tauri-drag-region />
+ )}
+ <App />
+ </PlatformProvider>
+ )
+}, root!)
diff --git a/packages/desktop/src/menu.ts b/packages/desktop/src/menu.ts
new file mode 100644
index 000000000..d1a5fba8e
--- /dev/null
+++ b/packages/desktop/src/menu.ts
@@ -0,0 +1,94 @@
+import { Menu, MenuItem, PredefinedMenuItem, Submenu } from "@tauri-apps/api/menu"
+import { type as ostype } from "@tauri-apps/plugin-os"
+
+import { runUpdater, UPDATER_ENABLED } from "./updater"
+
+export async function createMenu() {
+ if (ostype() !== "macos") return
+
+ const menu = await Menu.new({
+ items: [
+ await Submenu.new({
+ text: "OpenCode",
+ items: [
+ await PredefinedMenuItem.new({
+ item: { About: null },
+ }),
+ await MenuItem.new({
+ enabled: UPDATER_ENABLED,
+ action: () => runUpdater({ alertOnFail: true }),
+ text: "Check For Updates...",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Separator",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Hide",
+ }),
+ await PredefinedMenuItem.new({
+ item: "HideOthers",
+ }),
+ await PredefinedMenuItem.new({
+ item: "ShowAll",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Separator",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Quit",
+ }),
+ ].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: "Edit",
+ items: [
+ await PredefinedMenuItem.new({
+ item: "Undo",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Redo",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Separator",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Cut",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Copy",
+ }),
+ await PredefinedMenuItem.new({
+ item: "Paste",
+ }),
+ await PredefinedMenuItem.new({
+ item: "SelectAll",
+ }),
+ ],
+ }),
+ ],
+ })
+ menu.setAsAppMenu()
+}
diff --git a/packages/desktop/src/updater.ts b/packages/desktop/src/updater.ts
new file mode 100644
index 000000000..0a14026e4
--- /dev/null
+++ b/packages/desktop/src/updater.ts
@@ -0,0 +1,45 @@
+import { check } from "@tauri-apps/plugin-updater"
+import { relaunch } from "@tauri-apps/plugin-process"
+import { ask, message } from "@tauri-apps/plugin-dialog"
+import { invoke } from "@tauri-apps/api/core"
+
+export const UPDATER_ENABLED = window.__OPENCODE__?.updaterEnabled ?? false
+
+export async function runUpdater({ alertOnFail }: { alertOnFail: boolean }) {
+ let update
+ try {
+ update = await check()
+ } catch {
+ if (alertOnFail) await message("Failed to check for updates", { title: "Update Check Failed" })
+ return
+ }
+
+ if (!update) {
+ if (alertOnFail)
+ await message("You are already using the latest version of OpenCode", { title: "No Update Available" })
+ return
+ }
+
+ try {
+ await update.download()
+ } catch {
+ if (alertOnFail) await message("Failed to download update", { title: "Update Failed" })
+ return
+ }
+
+ const shouldUpdate = await ask(
+ `Version ${update.version} of OpenCode has been downloaded, would you like to install it and relaunch?`,
+ { title: "Update Downloaded" },
+ )
+ if (!shouldUpdate) return
+
+ try {
+ await update.install()
+ } catch {
+ await message("Failed to install update", { title: "Update Failed" })
+ return
+ }
+
+ await invoke("kill_sidecar")
+ await relaunch()
+}