feat(app): update settings in general settings
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
import { Component, createMemo, type JSX } from "solid-js"
|
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 { Select } from "@opencode-ai/ui/select"
|
||||||
import { Switch } from "@opencode-ai/ui/switch"
|
import { Switch } from "@opencode-ai/ui/switch"
|
||||||
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme"
|
||||||
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { useLanguage } from "@/context/language"
|
import { useLanguage } from "@/context/language"
|
||||||
|
import { usePlatform } from "@/context/platform"
|
||||||
import { useSettings, monoFontFamily } from "@/context/settings"
|
import { useSettings, monoFontFamily } from "@/context/settings"
|
||||||
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
|
import { playSound, SOUND_OPTIONS } from "@/utils/sound"
|
||||||
import { Link } from "./link"
|
import { Link } from "./link"
|
||||||
@@ -29,8 +33,67 @@ const playDemoSound = (src: string) => {
|
|||||||
export const SettingsGeneral: Component = () => {
|
export const SettingsGeneral: Component = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const language = useLanguage()
|
const language = useLanguage()
|
||||||
|
const platform = usePlatform()
|
||||||
const settings = useSettings()
|
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(() =>
|
const themeOptions = createMemo(() =>
|
||||||
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
|
Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })),
|
||||||
)
|
)
|
||||||
@@ -208,23 +271,6 @@ export const SettingsGeneral: Component = () => {
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Sound effects Section */}
|
||||||
<div class="flex flex-col gap-1">
|
<div class="flex flex-col gap-1">
|
||||||
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.general.section.sounds")}</h3>
|
<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>
|
</SettingsRow>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ export interface Settings {
|
|||||||
autoSave: boolean
|
autoSave: boolean
|
||||||
releaseNotes: boolean
|
releaseNotes: boolean
|
||||||
}
|
}
|
||||||
|
updates: {
|
||||||
|
startup: boolean
|
||||||
|
}
|
||||||
appearance: {
|
appearance: {
|
||||||
fontSize: number
|
fontSize: number
|
||||||
font: string
|
font: string
|
||||||
@@ -37,6 +40,9 @@ const defaultSettings: Settings = {
|
|||||||
autoSave: true,
|
autoSave: true,
|
||||||
releaseNotes: true,
|
releaseNotes: true,
|
||||||
},
|
},
|
||||||
|
updates: {
|
||||||
|
startup: true,
|
||||||
|
},
|
||||||
appearance: {
|
appearance: {
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
font: "ibm-plex-mono",
|
font: "ibm-plex-mono",
|
||||||
@@ -104,6 +110,12 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
|||||||
setStore("general", "releaseNotes", value)
|
setStore("general", "releaseNotes", value)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
updates: {
|
||||||
|
startup: createMemo(() => store.updates?.startup ?? defaultSettings.updates.startup),
|
||||||
|
setStartup(value: boolean) {
|
||||||
|
setStore("updates", "startup", value)
|
||||||
|
},
|
||||||
|
},
|
||||||
appearance: {
|
appearance: {
|
||||||
fontSize: createMemo(() => store.appearance?.fontSize ?? defaultSettings.appearance.fontSize),
|
fontSize: createMemo(() => store.appearance?.fontSize ?? defaultSettings.appearance.fontSize),
|
||||||
setFontSize(value: number) {
|
setFontSize(value: number) {
|
||||||
|
|||||||
@@ -540,6 +540,15 @@ export const dict = {
|
|||||||
|
|
||||||
"settings.general.row.releaseNotes.title": "Release notes",
|
"settings.general.row.releaseNotes.title": "Release notes",
|
||||||
"settings.general.row.releaseNotes.description": "Show What's New popups after updates",
|
"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.ibmPlexMono": "IBM Plex Mono",
|
||||||
"font.option.cascadiaCode": "Cascadia Code",
|
"font.option.cascadiaCode": "Cascadia Code",
|
||||||
"font.option.firaCode": "Fira Code",
|
"font.option.firaCode": "Fira Code",
|
||||||
|
|||||||
@@ -332,6 +332,7 @@ export default function Layout(props: ParentProps) {
|
|||||||
if (!platform.checkUpdate || !platform.update || !platform.restart) return
|
if (!platform.checkUpdate || !platform.update || !platform.restart) return
|
||||||
|
|
||||||
let toastId: number | undefined
|
let toastId: number | undefined
|
||||||
|
let interval: ReturnType<typeof setInterval> | undefined
|
||||||
|
|
||||||
async function pollUpdate() {
|
async function pollUpdate() {
|
||||||
const { updateAvailable, version } = await platform.checkUpdate!()
|
const { updateAvailable, version } = await platform.checkUpdate!()
|
||||||
@@ -358,9 +359,25 @@ export default function Layout(props: ParentProps) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pollUpdate()
|
createEffect(() => {
|
||||||
const interval = setInterval(pollUpdate, 10 * 60 * 1000)
|
if (!settings.ready()) return
|
||||||
onCleanup(() => clearInterval(interval))
|
|
||||||
|
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(() => {
|
onMount(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user