chore: cleanup

This commit is contained in:
Adam
2026-01-20 07:10:40 -06:00
parent dd5b5f5482
commit 340285575b
4 changed files with 142 additions and 18 deletions

View File

@@ -77,7 +77,15 @@ export const SettingsGeneral: Component = () => {
current={themeOptions().find((o) => o.id === theme.themeId())}
value={(o) => o.id}
label={(o) => o.name}
onSelect={(option) => option && theme.setTheme(option.id)}
onSelect={(option) => {
if (!option) return
theme.setTheme(option.id)
}}
onHighlight={(option) => {
if (!option) return
theme.previewTheme(option.id)
return () => theme.cancelPreview()
}}
variant="secondary"
size="small"
/>
@@ -135,6 +143,10 @@ export const SettingsGeneral: Component = () => {
current={soundOptions.find((o) => o.id === settings.sounds.agent())}
value={(o) => o.id}
label={(o) => o.label}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setAgent(option.id)
@@ -151,6 +163,10 @@ export const SettingsGeneral: Component = () => {
current={soundOptions.find((o) => o.id === settings.sounds.permissions())}
value={(o) => o.id}
label={(o) => o.label}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setPermissions(option.id)
@@ -167,6 +183,10 @@ export const SettingsGeneral: Component = () => {
current={soundOptions.find((o) => o.id === settings.sounds.errors())}
value={(o) => o.id}
label={(o) => o.label}
onHighlight={(option) => {
if (!option) return
playSound(option.src)
}}
onSelect={(option) => {
if (!option) return
settings.sounds.setErrors(option.id)

View File

@@ -124,13 +124,23 @@ export const SettingsKeybinds: Component = () => {
const out = new Map<string, KeybindMeta>()
out.set(PALETTE_ID, { title: "Command palette", group: "General" })
for (const opt of command.catalog) {
if (opt.id.startsWith("suggested.")) continue
out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
}
for (const opt of command.options) {
if (opt.id.startsWith("suggested.")) continue
out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
}
out.set(opt.id, {
title: opt.title,
group: groupFor(opt.id),
})
const keybinds = settings.current.keybinds as Record<string, string | undefined> | undefined
if (keybinds) {
for (const [id, value] of Object.entries(keybinds)) {
if (typeof value !== "string") continue
if (out.has(id)) continue
out.set(id, { title: id, group: groupFor(id) })
}
}
return out
@@ -181,11 +191,21 @@ export const SettingsKeybinds: Component = () => {
add(sig, { id: PALETTE_ID, title: "Command palette" })
}
for (const opt of command.options) {
if (opt.id.startsWith("suggested.")) continue
if (!opt.keybind) continue
for (const sig of signatures(opt.keybind)) {
add(sig, { id: opt.id, title: opt.title })
const valueFor = (id: string) => {
const custom = settings.keybinds.get(id)
if (typeof custom === "string") return custom
const live = command.options.find((x) => x.id === id)
if (live?.keybind) return live.keybind
const meta = command.catalog.find((x) => x.id === id)
return meta?.keybind
}
for (const id of list().keys()) {
if (id === PALETTE_ID) continue
for (const sig of signatures(valueFor(id))) {
add(sig, { id, title: title(id) })
}
}

View File

@@ -1,7 +1,9 @@
import { createMemo, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
import { createEffect, createMemo, createSignal, onCleanup, onMount, type Accessor } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { useSettings } from "@/context/settings"
import { Persist, persisted } from "@/utils/persist"
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
@@ -44,6 +46,14 @@ export interface CommandOption {
onHighlight?: () => (() => void) | void
}
export type CommandCatalogItem = {
title: string
description?: string
category?: string
keybind?: KeybindConfig
slash?: string
}
export function parseKeybind(config: string): Keybind[] {
if (!config || config === "none") return []
@@ -148,6 +158,11 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
const [suspendCount, setSuspendCount] = createSignal(0)
const [catalog, setCatalog, _, catalogReady] = persisted(
Persist.global("command.catalog.v1"),
createStore<Record<string, CommandCatalogItem>>({}),
)
const bind = (id: string, def: KeybindConfig | undefined) => {
const custom = settings.keybinds.get(actionId(id))
const config = custom ?? def
@@ -155,7 +170,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
return config
}
const options = createMemo(() => {
const registered = createMemo(() => {
const seen = new Set<string>()
const all: CommandOption[] = []
@@ -167,7 +182,28 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
}
}
const resolved = all.map((opt) => ({
return all
})
createEffect(() => {
if (!catalogReady()) return
for (const opt of registered()) {
const id = actionId(opt.id)
setCatalog(id, {
title: opt.title,
description: opt.description,
category: opt.category,
keybind: opt.keybind,
slash: opt.slash,
})
}
})
const catalogOptions = createMemo(() => Object.entries(catalog).map(([id, meta]) => ({ id, ...meta })))
const options = createMemo(() => {
const resolved = registered().map((opt) => ({
...opt,
keybind: bind(opt.id, opt.keybind),
}))
@@ -246,15 +282,23 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND)
}
const option = options().find((x) => x.id === id || x.id === SUGGESTED_PREFIX + id)
if (!option?.keybind) return ""
return formatKeybind(option.keybind)
const base = actionId(id)
const option = options().find((x) => actionId(x.id) === base)
if (option?.keybind) return formatKeybind(option.keybind)
const meta = catalog[base]
const config = bind(base, meta?.keybind)
if (!config) return ""
return formatKeybind(config)
},
show: showPalette,
keybinds(enabled: boolean) {
setSuspendCount((count) => count + (enabled ? -1 : 1))
},
suspended,
get catalog() {
return catalogOptions()
},
get options() {
return options()
},

View File

@@ -1,5 +1,5 @@
import { Select as Kobalte } from "@kobalte/core/select"
import { createMemo, splitProps, type ComponentProps, type JSX } from "solid-js"
import { createMemo, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js"
import { pipe, groupBy, entries, map } from "remeda"
import { Button, ButtonProps } from "./button"
import { Icon } from "./icon"
@@ -12,6 +12,7 @@ export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "
label?: (x: T) => string
groupBy?: (x: T) => string
onSelect?: (value: T | undefined) => void
onHighlight?: (value: T | undefined) => (() => void) | void
class?: ComponentProps<"div">["class"]
classList?: ComponentProps<"div">["classList"]
children?: (item: T | undefined) => JSX.Element
@@ -28,8 +29,40 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
"label",
"groupBy",
"onSelect",
"onHighlight",
"onOpenChange",
"children",
])
const state = {
key: undefined as string | undefined,
cleanup: undefined as (() => void) | void,
}
const stop = () => {
state.cleanup?.()
state.cleanup = undefined
state.key = undefined
}
const keyFor = (item: T) => (local.value ? local.value(item) : (item as string))
const move = (item: T | undefined) => {
if (!local.onHighlight) return
if (!item) {
stop()
return
}
const key = keyFor(item)
if (state.key === key) return
state.cleanup?.()
state.cleanup = local.onHighlight(item)
state.key = key
}
onCleanup(stop)
const grouped = createMemo(() => {
const result = pipe(
local.options,
@@ -58,12 +91,14 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
)}
itemComponent={(itemProps) => (
<Kobalte.Item
{...itemProps}
data-slot="select-select-item"
classList={{
...(local.classList ?? {}),
[local.class ?? ""]: !!local.class,
}}
{...itemProps}
onPointerEnter={() => move(itemProps.item.rawValue)}
onPointerMove={() => move(itemProps.item.rawValue)}
>
<Kobalte.ItemLabel data-slot="select-select-item-label">
{local.children
@@ -79,6 +114,11 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
)}
onChange={(v) => {
local.onSelect?.(v ?? undefined)
stop()
}}
onOpenChange={(open) => {
local.onOpenChange?.(open)
if (!open) stop()
}}
>
<Kobalte.Trigger