diff options
| author | adamelmore <[email protected]> | 2026-01-27 08:30:23 -0600 |
|---|---|---|
| committer | adamelmore <[email protected]> | 2026-01-27 11:38:35 -0600 |
| commit | 2f5a238b511e3ac4d8235da4d6e7fdbaf06c9b84 (patch) | |
| tree | 1bcbd4d51758721d6cbcc3d36e97c6a76ceef5c9 | |
| parent | 173faca3c6a8d29e4502fbedaa7a56cdddd05c10 (diff) | |
| download | opencode-2f5a238b511e3ac4d8235da4d6e7fdbaf06c9b84.tar.gz opencode-2f5a238b511e3ac4d8235da4d6e7fdbaf06c9b84.zip | |
feat(app): update settings in general settings
| -rw-r--r-- | packages/app/src/components/settings-general.tsx | 124 | ||||
| -rw-r--r-- | packages/app/src/context/settings.tsx | 12 | ||||
| -rw-r--r-- | packages/app/src/i18n/en.ts | 9 | ||||
| -rw-r--r-- | packages/app/src/pages/layout.tsx | 23 |
4 files changed, 148 insertions, 20 deletions
diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index 57efcfdfa..180a99c73 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -1,8 +1,12 @@ import { Component, createMemo, type JSX } from "solid-js" +import { createStore } from "solid-js/store" +import { Button } from "@opencode-ai/ui/button" import { Select } from "@opencode-ai/ui/select" import { Switch } from "@opencode-ai/ui/switch" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme" +import { showToast } from "@opencode-ai/ui/toast" import { useLanguage } from "@/context/language" +import { usePlatform } from "@/context/platform" import { useSettings, monoFontFamily } from "@/context/settings" import { playSound, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "./link" @@ -29,8 +33,67 @@ const playDemoSound = (src: string) => { export const SettingsGeneral: Component = () => { const theme = useTheme() const language = useLanguage() + const platform = usePlatform() const settings = useSettings() + const [store, setStore] = createStore({ + checking: false, + }) + + const check = () => { + if (!platform.checkUpdate) return + setStore("checking", true) + + void platform + .checkUpdate() + .then((result) => { + if (!result.updateAvailable) { + showToast({ + variant: "success", + icon: "circle-check", + title: language.t("settings.updates.toast.latest.title"), + description: language.t("settings.updates.toast.latest.description", { version: platform.version ?? "" }), + }) + return + } + + const actions = + platform.update && platform.restart + ? [ + { + label: language.t("toast.update.action.installRestart"), + onClick: async () => { + await platform.update!() + await platform.restart!() + }, + }, + { + label: language.t("toast.update.action.notYet"), + onClick: "dismiss" as const, + }, + ] + : [ + { + label: language.t("toast.update.action.notYet"), + onClick: "dismiss" as const, + }, + ] + + showToast({ + persistent: true, + icon: "download", + title: language.t("toast.update.title"), + description: language.t("toast.update.description", { version: result.version ?? "" }), + actions, + }) + }) + .catch((err: unknown) => { + const message = err instanceof Error ? err.message : String(err) + showToast({ title: language.t("common.requestFailed"), description: message }) + }) + .finally(() => setStore("checking", false)) + } + const themeOptions = createMemo(() => Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })), ) @@ -208,23 +271,6 @@ export const SettingsGeneral: Component = () => { </div> </div> - {/* Updates Section */} - <div class="flex flex-col gap-1"> - <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.updates")}</h3> - - <div class="bg-surface-raised-base px-4 rounded-lg"> - <SettingsRow - title={language.t("settings.general.row.releaseNotes.title")} - description={language.t("settings.general.row.releaseNotes.description")} - > - <Switch - checked={settings.general.releaseNotes()} - onChange={(checked) => settings.general.setReleaseNotes(checked)} - /> - </SettingsRow> - </div> - </div> - {/* Sound effects Section */} <div class="flex flex-col gap-1"> <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.sounds")}</h3> @@ -303,6 +349,50 @@ export const SettingsGeneral: Component = () => { </SettingsRow> </div> </div> + + {/* Updates Section */} + <div class="flex flex-col gap-1"> + <h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.updates")}</h3> + + <div class="bg-surface-raised-base px-4 rounded-lg"> + <SettingsRow + title={language.t("settings.updates.row.startup.title")} + description={language.t("settings.updates.row.startup.description")} + > + <Switch + checked={settings.updates.startup()} + disabled={!platform.checkUpdate} + onChange={(checked) => settings.updates.setStartup(checked)} + /> + </SettingsRow> + + <SettingsRow + title={language.t("settings.general.row.releaseNotes.title")} + description={language.t("settings.general.row.releaseNotes.description")} + > + <Switch + checked={settings.general.releaseNotes()} + onChange={(checked) => settings.general.setReleaseNotes(checked)} + /> + </SettingsRow> + + <SettingsRow + title={language.t("settings.updates.row.check.title")} + description={language.t("settings.updates.row.check.description")} + > + <Button + size="small" + variant="secondary" + disabled={store.checking || !platform.checkUpdate} + onClick={check} + > + {store.checking + ? language.t("settings.updates.action.checking") + : language.t("settings.updates.action.checkNow")} + </Button> + </SettingsRow> + </div> + </div> </div> </div> ) diff --git a/packages/app/src/context/settings.tsx b/packages/app/src/context/settings.tsx index 67e907a63..19b3846f8 100644 --- a/packages/app/src/context/settings.tsx +++ b/packages/app/src/context/settings.tsx @@ -20,6 +20,9 @@ export interface Settings { autoSave: boolean releaseNotes: boolean } + updates: { + startup: boolean + } appearance: { fontSize: number font: string @@ -37,6 +40,9 @@ const defaultSettings: Settings = { autoSave: true, releaseNotes: true, }, + updates: { + startup: true, + }, appearance: { fontSize: 14, font: "ibm-plex-mono", @@ -104,6 +110,12 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont setStore("general", "releaseNotes", value) }, }, + updates: { + startup: createMemo(() => store.updates?.startup ?? defaultSettings.updates.startup), + setStartup(value: boolean) { + setStore("updates", "startup", value) + }, + }, appearance: { fontSize: createMemo(() => store.appearance?.fontSize ?? defaultSettings.appearance.fontSize), setFontSize(value: number) { diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index abbe497dc..8fb819798 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -540,6 +540,15 @@ export const dict = { "settings.general.row.releaseNotes.title": "Release notes", "settings.general.row.releaseNotes.description": "Show What's New popups after updates", + + "settings.updates.row.startup.title": "Check for updates on startup", + "settings.updates.row.startup.description": "Automatically check for updates when OpenCode launches", + "settings.updates.row.check.title": "Check for updates", + "settings.updates.row.check.description": "Manually check for updates and install if available", + "settings.updates.action.checkNow": "Check now", + "settings.updates.action.checking": "Checking...", + "settings.updates.toast.latest.title": "You're up to date", + "settings.updates.toast.latest.description": "You're running the latest version of OpenCode.", "font.option.ibmPlexMono": "IBM Plex Mono", "font.option.cascadiaCode": "Cascadia Code", "font.option.firaCode": "Fira Code", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 15557dedb..82a3fa6c9 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -332,6 +332,7 @@ export default function Layout(props: ParentProps) { if (!platform.checkUpdate || !platform.update || !platform.restart) return let toastId: number | undefined + let interval: ReturnType<typeof setInterval> | undefined async function pollUpdate() { const { updateAvailable, version } = await platform.checkUpdate!() @@ -358,9 +359,25 @@ export default function Layout(props: ParentProps) { } } - pollUpdate() - const interval = setInterval(pollUpdate, 10 * 60 * 1000) - onCleanup(() => clearInterval(interval)) + createEffect(() => { + if (!settings.ready()) return + + if (!settings.updates.startup()) { + if (interval === undefined) return + clearInterval(interval) + interval = undefined + return + } + + if (interval !== undefined) return + void pollUpdate() + interval = setInterval(pollUpdate, 10 * 60 * 1000) + }) + + onCleanup(() => { + if (interval === undefined) return + clearInterval(interval) + }) }) onMount(() => { |
