chore: cleanup
This commit is contained in:
@@ -77,7 +77,15 @@ export const SettingsGeneral: Component = () => {
|
|||||||
current={themeOptions().find((o) => o.id === theme.themeId())}
|
current={themeOptions().find((o) => o.id === theme.themeId())}
|
||||||
value={(o) => o.id}
|
value={(o) => o.id}
|
||||||
label={(o) => o.name}
|
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"
|
variant="secondary"
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
@@ -135,6 +143,10 @@ export const SettingsGeneral: Component = () => {
|
|||||||
current={soundOptions.find((o) => o.id === settings.sounds.agent())}
|
current={soundOptions.find((o) => o.id === settings.sounds.agent())}
|
||||||
value={(o) => o.id}
|
value={(o) => o.id}
|
||||||
label={(o) => o.label}
|
label={(o) => o.label}
|
||||||
|
onHighlight={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
playSound(option.src)
|
||||||
|
}}
|
||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
if (!option) return
|
if (!option) return
|
||||||
settings.sounds.setAgent(option.id)
|
settings.sounds.setAgent(option.id)
|
||||||
@@ -151,6 +163,10 @@ export const SettingsGeneral: Component = () => {
|
|||||||
current={soundOptions.find((o) => o.id === settings.sounds.permissions())}
|
current={soundOptions.find((o) => o.id === settings.sounds.permissions())}
|
||||||
value={(o) => o.id}
|
value={(o) => o.id}
|
||||||
label={(o) => o.label}
|
label={(o) => o.label}
|
||||||
|
onHighlight={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
playSound(option.src)
|
||||||
|
}}
|
||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
if (!option) return
|
if (!option) return
|
||||||
settings.sounds.setPermissions(option.id)
|
settings.sounds.setPermissions(option.id)
|
||||||
@@ -167,6 +183,10 @@ export const SettingsGeneral: Component = () => {
|
|||||||
current={soundOptions.find((o) => o.id === settings.sounds.errors())}
|
current={soundOptions.find((o) => o.id === settings.sounds.errors())}
|
||||||
value={(o) => o.id}
|
value={(o) => o.id}
|
||||||
label={(o) => o.label}
|
label={(o) => o.label}
|
||||||
|
onHighlight={(option) => {
|
||||||
|
if (!option) return
|
||||||
|
playSound(option.src)
|
||||||
|
}}
|
||||||
onSelect={(option) => {
|
onSelect={(option) => {
|
||||||
if (!option) return
|
if (!option) return
|
||||||
settings.sounds.setErrors(option.id)
|
settings.sounds.setErrors(option.id)
|
||||||
|
|||||||
@@ -124,13 +124,23 @@ export const SettingsKeybinds: Component = () => {
|
|||||||
const out = new Map<string, KeybindMeta>()
|
const out = new Map<string, KeybindMeta>()
|
||||||
out.set(PALETTE_ID, { title: "Command palette", group: "General" })
|
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) {
|
for (const opt of command.options) {
|
||||||
if (opt.id.startsWith("suggested.")) continue
|
if (opt.id.startsWith("suggested.")) continue
|
||||||
|
out.set(opt.id, { title: opt.title, group: groupFor(opt.id) })
|
||||||
|
}
|
||||||
|
|
||||||
out.set(opt.id, {
|
const keybinds = settings.current.keybinds as Record<string, string | undefined> | undefined
|
||||||
title: opt.title,
|
if (keybinds) {
|
||||||
group: groupFor(opt.id),
|
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
|
return out
|
||||||
@@ -181,11 +191,21 @@ export const SettingsKeybinds: Component = () => {
|
|||||||
add(sig, { id: PALETTE_ID, title: "Command palette" })
|
add(sig, { id: PALETTE_ID, title: "Command palette" })
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const opt of command.options) {
|
const valueFor = (id: string) => {
|
||||||
if (opt.id.startsWith("suggested.")) continue
|
const custom = settings.keybinds.get(id)
|
||||||
if (!opt.keybind) continue
|
if (typeof custom === "string") return custom
|
||||||
for (const sig of signatures(opt.keybind)) {
|
|
||||||
add(sig, { id: opt.id, title: opt.title })
|
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) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 { createSimpleContext } from "@opencode-ai/ui/context"
|
||||||
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
import { useDialog } from "@opencode-ai/ui/context/dialog"
|
||||||
import { useSettings } from "@/context/settings"
|
import { useSettings } from "@/context/settings"
|
||||||
|
import { Persist, persisted } from "@/utils/persist"
|
||||||
|
|
||||||
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
|
||||||
|
|
||||||
@@ -44,6 +46,14 @@ export interface CommandOption {
|
|||||||
onHighlight?: () => (() => void) | void
|
onHighlight?: () => (() => void) | void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CommandCatalogItem = {
|
||||||
|
title: string
|
||||||
|
description?: string
|
||||||
|
category?: string
|
||||||
|
keybind?: KeybindConfig
|
||||||
|
slash?: string
|
||||||
|
}
|
||||||
|
|
||||||
export function parseKeybind(config: string): Keybind[] {
|
export function parseKeybind(config: string): Keybind[] {
|
||||||
if (!config || config === "none") return []
|
if (!config || config === "none") return []
|
||||||
|
|
||||||
@@ -148,6 +158,11 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
|
const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
|
||||||
const [suspendCount, setSuspendCount] = createSignal(0)
|
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 bind = (id: string, def: KeybindConfig | undefined) => {
|
||||||
const custom = settings.keybinds.get(actionId(id))
|
const custom = settings.keybinds.get(actionId(id))
|
||||||
const config = custom ?? def
|
const config = custom ?? def
|
||||||
@@ -155,7 +170,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = createMemo(() => {
|
const registered = createMemo(() => {
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
const all: CommandOption[] = []
|
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,
|
...opt,
|
||||||
keybind: bind(opt.id, opt.keybind),
|
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)
|
return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND)
|
||||||
}
|
}
|
||||||
|
|
||||||
const option = options().find((x) => x.id === id || x.id === SUGGESTED_PREFIX + id)
|
const base = actionId(id)
|
||||||
if (!option?.keybind) return ""
|
const option = options().find((x) => actionId(x.id) === base)
|
||||||
return formatKeybind(option.keybind)
|
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,
|
show: showPalette,
|
||||||
keybinds(enabled: boolean) {
|
keybinds(enabled: boolean) {
|
||||||
setSuspendCount((count) => count + (enabled ? -1 : 1))
|
setSuspendCount((count) => count + (enabled ? -1 : 1))
|
||||||
},
|
},
|
||||||
suspended,
|
suspended,
|
||||||
|
get catalog() {
|
||||||
|
return catalogOptions()
|
||||||
|
},
|
||||||
get options() {
|
get options() {
|
||||||
return options()
|
return options()
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Select as Kobalte } from "@kobalte/core/select"
|
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 { pipe, groupBy, entries, map } from "remeda"
|
||||||
import { Button, ButtonProps } from "./button"
|
import { Button, ButtonProps } from "./button"
|
||||||
import { Icon } from "./icon"
|
import { Icon } from "./icon"
|
||||||
@@ -12,6 +12,7 @@ export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "
|
|||||||
label?: (x: T) => string
|
label?: (x: T) => string
|
||||||
groupBy?: (x: T) => string
|
groupBy?: (x: T) => string
|
||||||
onSelect?: (value: T | undefined) => void
|
onSelect?: (value: T | undefined) => void
|
||||||
|
onHighlight?: (value: T | undefined) => (() => void) | void
|
||||||
class?: ComponentProps<"div">["class"]
|
class?: ComponentProps<"div">["class"]
|
||||||
classList?: ComponentProps<"div">["classList"]
|
classList?: ComponentProps<"div">["classList"]
|
||||||
children?: (item: T | undefined) => JSX.Element
|
children?: (item: T | undefined) => JSX.Element
|
||||||
@@ -28,8 +29,40 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
|
|||||||
"label",
|
"label",
|
||||||
"groupBy",
|
"groupBy",
|
||||||
"onSelect",
|
"onSelect",
|
||||||
|
"onHighlight",
|
||||||
|
"onOpenChange",
|
||||||
"children",
|
"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 grouped = createMemo(() => {
|
||||||
const result = pipe(
|
const result = pipe(
|
||||||
local.options,
|
local.options,
|
||||||
@@ -58,12 +91,14 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
|
|||||||
)}
|
)}
|
||||||
itemComponent={(itemProps) => (
|
itemComponent={(itemProps) => (
|
||||||
<Kobalte.Item
|
<Kobalte.Item
|
||||||
|
{...itemProps}
|
||||||
data-slot="select-select-item"
|
data-slot="select-select-item"
|
||||||
classList={{
|
classList={{
|
||||||
...(local.classList ?? {}),
|
...(local.classList ?? {}),
|
||||||
[local.class ?? ""]: !!local.class,
|
[local.class ?? ""]: !!local.class,
|
||||||
}}
|
}}
|
||||||
{...itemProps}
|
onPointerEnter={() => move(itemProps.item.rawValue)}
|
||||||
|
onPointerMove={() => move(itemProps.item.rawValue)}
|
||||||
>
|
>
|
||||||
<Kobalte.ItemLabel data-slot="select-select-item-label">
|
<Kobalte.ItemLabel data-slot="select-select-item-label">
|
||||||
{local.children
|
{local.children
|
||||||
@@ -79,6 +114,11 @@ export function Select<T>(props: SelectProps<T> & ButtonProps) {
|
|||||||
)}
|
)}
|
||||||
onChange={(v) => {
|
onChange={(v) => {
|
||||||
local.onSelect?.(v ?? undefined)
|
local.onSelect?.(v ?? undefined)
|
||||||
|
stop()
|
||||||
|
}}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
local.onOpenChange?.(open)
|
||||||
|
if (!open) stop()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Kobalte.Trigger
|
<Kobalte.Trigger
|
||||||
|
|||||||
Reference in New Issue
Block a user