diff options
| author | Adam <[email protected]> | 2025-12-22 20:01:16 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-22 20:01:25 -0600 |
| commit | 81c3c638953fb7730a6965b3614d57e3c4919dde (patch) | |
| tree | bea87a6b98078d52dd037da16500b29a28587edc /packages/desktop/src | |
| parent | b76bd4141dd78db94ab485743b4dc21d918cd85e (diff) | |
| download | opencode-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.tsx | 114 | ||||
| -rw-r--r-- | packages/desktop/src/menu.ts | 94 | ||||
| -rw-r--r-- | packages/desktop/src/updater.ts | 45 |
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() +} |
