import { Component, Show, createMemo, createResource, onMount, type JSX } from "solid-js" import { createStore } from "solid-js/store" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" import { Select } from "@opencode-ai/ui/select" import { Switch } from "@opencode-ai/ui/switch" import { TextField } from "@opencode-ai/ui/text-field" import { Tooltip } from "@opencode-ai/ui/tooltip" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme/context" import { showToast } from "@opencode-ai/ui/toast" import { useParams } from "@solidjs/router" import { useLanguage } from "@/context/language" import { usePermission } from "@/context/permission" import { usePlatform } from "@/context/platform" import { monoDefault, monoFontFamily, monoInput, sansDefault, sansFontFamily, sansInput, terminalDefault, terminalFontFamily, terminalInput, useSettings, } from "@/context/settings" import { decode64 } from "@/utils/base64" import { playSoundById, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "./link" import { SettingsList } from "./settings-list" let demoSoundState = { cleanup: undefined as (() => void) | undefined, timeout: undefined as NodeJS.Timeout | undefined, run: 0, } type ThemeOption = { id: string name: string } // To prevent audio from overlapping/playing very quickly when navigating the settings menus, // delay the playback by 100ms during quick selection changes and pause existing sounds. const stopDemoSound = () => { demoSoundState.run += 1 if (demoSoundState.cleanup) { demoSoundState.cleanup() } clearTimeout(demoSoundState.timeout) demoSoundState.cleanup = undefined } const playDemoSound = (id: string | undefined) => { stopDemoSound() if (!id) return const run = ++demoSoundState.run demoSoundState.timeout = setTimeout(() => { void playSoundById(id).then((cleanup) => { if (demoSoundState.run !== run) { cleanup?.() return } demoSoundState.cleanup = cleanup }) }, 100) } export const SettingsGeneral: Component = () => { const theme = useTheme() const language = useLanguage() const permission = usePermission() const platform = usePlatform() const params = useParams() const settings = useSettings() onMount(() => { void theme.loadThemes() }) const [store, setStore] = createStore({ checking: false, }) const linux = createMemo(() => platform.platform === "desktop" && platform.os === "linux") const dir = createMemo(() => decode64(params.dir)) const accepting = createMemo(() => { const value = dir() if (!value) return false if (!params.id) return permission.isAutoAcceptingDirectory(value) return permission.isAutoAccepting(params.id, value) }) const toggleAccept = (checked: boolean) => { const value = dir() if (!value) return if (!params.id) { if (permission.isAutoAcceptingDirectory(value) === checked) return permission.toggleAutoAcceptDirectory(value) return } if (checked) { permission.enableAutoAccept(params.id, value) return } permission.disableAutoAccept(params.id, value) } const desktop = createMemo(() => platform.platform === "desktop") 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(() => theme.ids().map((id) => ({ id, name: theme.name(id) }))) const colorSchemeOptions = createMemo((): { value: ColorScheme; label: string }[] => [ { value: "system", label: language.t("theme.scheme.system") }, { value: "light", label: language.t("theme.scheme.light") }, { value: "dark", label: language.t("theme.scheme.dark") }, ]) const languageOptions = createMemo(() => language.locales.map((locale) => ({ value: locale, label: language.label(locale), })), ) const noneSound = { id: "none", label: "sound.option.none" } as const const soundOptions = [noneSound, ...SOUND_OPTIONS] const mono = () => monoInput(settings.appearance.font()) const sans = () => sansInput(settings.appearance.uiFont()) const terminal = () => terminalInput(settings.appearance.terminalFont()) const soundSelectProps = ( enabled: () => boolean, current: () => string, setEnabled: (value: boolean) => void, set: (id: string) => void, ) => ({ options: soundOptions, current: enabled() ? (soundOptions.find((o) => o.id === current()) ?? noneSound) : noneSound, value: (o: (typeof soundOptions)[number]) => o.id, label: (o: (typeof soundOptions)[number]) => language.t(o.label), onHighlight: (option: (typeof soundOptions)[number] | undefined) => { if (!option) return playDemoSound(option.id === "none" ? undefined : option.id) }, onSelect: (option: (typeof soundOptions)[number] | undefined) => { if (!option) return if (option.id === "none") { setEnabled(false) stopDemoSound() return } setEnabled(true) set(option.id) playDemoSound(option.id) }, variant: "secondary" as const, size: "small" as const, triggerVariant: "settings" as const, }) const GeneralSection = () => (
o.value === theme.colorScheme())} value={(o) => o.value} label={(o) => o.label} onSelect={(option) => option && theme.setColorScheme(option.value)} onHighlight={(option) => { if (!option) return theme.previewColorScheme(option.value) return () => theme.cancelPreview() }} variant="secondary" size="small" triggerVariant="settings" triggerStyle={{ "min-width": "220px" }} /> {language.t("settings.general.row.theme.description")}{" "} {language.t("common.learnMore")} } > settings.sounds.agentEnabled(), () => settings.sounds.agent(), (value) => settings.sounds.setAgentEnabled(value), (id) => settings.sounds.setAgent(id), )} /> settings.sounds.errorsEnabled(), () => settings.sounds.errors(), (value) => settings.sounds.setErrorsEnabled(value), (id) => settings.sounds.setErrors(id), )} />
) const UpdatesSection = () => (

{language.t("settings.general.section.updates")}

settings.updates.setStartup(checked)} />
settings.general.setReleaseNotes(checked)} />
) console.log(import.meta.env) return (

{language.t("settings.tab.general")}

{/* {(_) => { const [enabledResource, actions] = createResource(() => platform.getWslEnabled?.()) const enabled = () => (enabledResource.state === "pending" ? undefined : enabledResource.latest) return (

{language.t("settings.desktop.section.wsl")}

platform.setWslEnabled?.(checked)?.finally(() => actions.refetch())} />
) }}
*/} {(_) => { const [valueResource, actions] = createResource(() => platform.getDisplayBackend?.()) const value = () => (valueResource.state === "pending" ? undefined : valueResource.latest) const onChange = (checked: boolean) => platform.setDisplayBackend?.(checked ? "wayland" : "auto").finally(() => actions.refetch()) return (

{language.t("settings.general.section.display")}

{language.t("settings.general.row.wayland.title")}
} description={language.t("settings.general.row.wayland.description")} >
) }}
) } interface SettingsRowProps { title: string | JSX.Element description: string | JSX.Element children: JSX.Element } const SettingsRow: Component = (props) => { return (
{props.title} {props.description}
{props.children}
) }