refine plugin manager dialog

This commit is contained in:
Sebastian Herrlinger
2026-03-26 23:39:06 +01:00
parent b958ffa930
commit 23ebfa0aee
4 changed files with 53 additions and 12 deletions
@@ -1,12 +1,18 @@
import { Keybind } from "@/util/keybind"
import type { TuiPlugin, TuiPluginApi, TuiPluginStatus } from "@opencode-ai/plugin/tui"
import { useTerminalDimensions } from "@opentui/solid"
import { fileURLToPath } from "url"
import { DialogSelect, type DialogSelectOption } from "@tui/ui/dialog-select"
import { createMemo, createSignal } from "solid-js"
import { createEffect, createMemo, createSignal } from "solid-js"
const id = "internal:plugin-manager"
const key = Keybind.parse("space").at(0)
function state(api: TuiPluginApi, item: TuiPluginStatus) {
if (!item.enabled) {
return <span style={{ fg: api.theme.current.textMuted }}>disabled</span>
}
return (
<span style={{ fg: item.active ? api.theme.current.success : api.theme.current.error }}>
{item.active ? "active" : "inactive"}
@@ -14,21 +20,51 @@ function state(api: TuiPluginApi, item: TuiPluginStatus) {
)
}
function row(api: TuiPluginApi, item: TuiPluginStatus): DialogSelectOption<string> {
function source(spec: string) {
if (!spec.startsWith("file://")) return
return fileURLToPath(spec)
}
function meta(item: TuiPluginStatus, width: number) {
if (item.source === "internal") {
if (width >= 120) return "Built-in plugin"
return "Built-in"
}
const next = source(item.spec)
if (next) return next
return item.spec
}
function row(api: TuiPluginApi, item: TuiPluginStatus, width: number): DialogSelectOption<string> {
return {
title: item.id,
value: item.id,
category: item.source === "internal" ? "Internal" : "External",
description: item.source === "internal" ? "Built-in" : item.spec,
description: meta(item, width),
footer: state(api, item),
disabled: item.id === id,
}
}
function View(props: { api: TuiPluginApi }) {
const size = useTerminalDimensions()
const [list, setList] = createSignal(props.api.plugins.list())
const [cur, setCur] = createSignal<string | undefined>()
const [lock, setLock] = createSignal(false)
createEffect(() => {
const width = size().width
if (width >= 128) {
props.api.ui.dialog.setSize("xlarge")
return
}
if (width >= 96) {
props.api.ui.dialog.setSize("large")
return
}
props.api.ui.dialog.setSize("medium")
})
const rows = createMemo(() =>
[...list()]
.sort((a, b) => {
@@ -37,7 +73,7 @@ function View(props: { api: TuiPluginApi }) {
if (x !== y) return x - y
return a.id.localeCompare(b.id)
})
.map((item) => row(props.api, item)),
.map((item) => row(props.api, item, size().width)),
)
const flip = (x: string) => {
@@ -9,7 +9,7 @@ import { Selection } from "@tui/util/selection"
export function Dialog(
props: ParentProps<{
size?: "medium" | "large"
size?: "medium" | "large" | "xlarge"
onClose: () => void
}>,
) {
@@ -18,6 +18,11 @@ export function Dialog(
const renderer = useRenderer()
let dismiss = false
const width = () => {
if (props.size === "xlarge") return 116
if (props.size === "large") return 88
return 60
}
return (
<box
@@ -46,7 +51,7 @@ export function Dialog(
dismiss = false
e.stopPropagation()
}}
width={props.size === "large" ? 80 : 60}
width={width()}
maxWidth={dimensions().width - 2}
backgroundColor={theme.backgroundPanel}
paddingTop={1}
@@ -63,7 +68,7 @@ function init() {
element: JSX.Element
onClose?: () => void
}[],
size: "medium" as "medium" | "large",
size: "medium" as "medium" | "large" | "xlarge",
})
const renderer = useRenderer()
@@ -136,7 +141,7 @@ function init() {
get size() {
return store.size
},
setSize(size: "medium" | "large") {
setSize(size: "medium" | "large" | "xlarge") {
setStore("size", size)
},
}