mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-21 18:26:09 +00:00
refactor(tui): migrate dialog views to V2 themes (#37516)
This commit is contained in:
@@ -16,7 +16,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||
const client = useClient()
|
||||
const dialog = useDialog()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const [loadError, setLoadError] = createSignal<unknown>()
|
||||
const [showPassword, setShowPassword] = createSignal(false)
|
||||
const [passwordHover, setPasswordHover] = createSignal(false)
|
||||
@@ -47,17 +47,17 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||
<box flexDirection={horizontal() ? "row" : "column"} alignItems={horizontal() ? "flex-start" : "center"} gap={2}>
|
||||
<box width={horizontal() ? 29 : "100%"} flexShrink={0} gap={1}>
|
||||
<box>
|
||||
<text fg={theme.textMuted}>URLs</text>
|
||||
<For each={value.urls}>{(url) => <text fg={theme.text}>{url}</text>}</For>
|
||||
<text fg={themeV2.text.subdued()}>URLs</text>
|
||||
<For each={value.urls}>{(url) => <text fg={themeV2.text()}>{url}</text>}</For>
|
||||
</box>
|
||||
<box>
|
||||
<text fg={theme.textMuted}>Username</text>
|
||||
<text fg={theme.text}>{value.username}</text>
|
||||
<text fg={themeV2.text.subdued()}>Username</text>
|
||||
<text fg={themeV2.text()}>{value.username}</text>
|
||||
</box>
|
||||
<box>
|
||||
<text fg={theme.textMuted}>Password</text>
|
||||
<text fg={themeV2.text.subdued()}>Password</text>
|
||||
<text
|
||||
fg={passwordHover() ? theme.text : theme.textMuted}
|
||||
fg={passwordHover() ? themeV2.text() : themeV2.text.subdued()}
|
||||
wrapMode="word"
|
||||
onMouseOver={() => setPasswordHover(true)}
|
||||
onMouseOut={() => setPasswordHover(false)}
|
||||
@@ -67,7 +67,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||
</text>
|
||||
</box>
|
||||
<Show when={value.urls.some((url) => ["localhost", "127.0.0.1", "[::1]"].includes(new URL(url).hostname))}>
|
||||
<text fg={theme.textMuted} wrapMode="word">
|
||||
<text fg={themeV2.text.subdued()} wrapMode="word">
|
||||
Run `opencode service set hostname 0.0.0.0` to access the service remotely.
|
||||
</text>
|
||||
</Show>
|
||||
@@ -78,7 +78,7 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||
flexShrink={0}
|
||||
alignItems={horizontal() ? "flex-end" : "center"}
|
||||
>
|
||||
<text fg={theme.text}>{renderUnicodeCompact(JSON.stringify(value), { border: 1 })}</text>
|
||||
<text fg={themeV2.text()}>{renderUnicodeCompact(JSON.stringify(value), { border: 1 })}</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
@@ -87,17 +87,17 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} paddingBottom={1} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||
<text fg={themeV2.text()} attributes={TextAttributes.BOLD}>
|
||||
Pair
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<Show
|
||||
when={loadError()}
|
||||
fallback={
|
||||
<Show when={info()} fallback={<text fg={theme.textMuted}>Loading server information…</text>}>
|
||||
<Show when={info()} fallback={<text fg={themeV2.text.subdued()}>Loading server information…</text>}>
|
||||
<Show
|
||||
when={dimensions().height >= 36}
|
||||
fallback={
|
||||
@@ -116,11 +116,11 @@ export function DialogPair(props: { credentials?: DialogPairCredentials }) {
|
||||
>
|
||||
{(error) => (
|
||||
<box>
|
||||
<text fg={theme.error} attributes={TextAttributes.BOLD}>
|
||||
<text fg={themeV2.text.feedback.error()} attributes={TextAttributes.BOLD}>
|
||||
Could not load server information
|
||||
</text>
|
||||
<text fg={theme.textMuted}>{errorMessage(error())}</text>
|
||||
<text fg={theme.textMuted}>Close and reopen Pair to try again.</text>
|
||||
<text fg={themeV2.text.subdued()}>{errorMessage(error())}</text>
|
||||
<text fg={themeV2.text.subdued()}>Close and reopen Pair to try again.</text>
|
||||
</box>
|
||||
)}
|
||||
</Show>
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useDialog, type DialogContext } from "../ui/dialog"
|
||||
|
||||
export function DialogProjectCopyName(props: { onConfirm: (name: string) => void }) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
const [inputTarget, setInputTarget] = createSignal<InputRenderable>()
|
||||
let input: InputRenderable
|
||||
@@ -47,10 +47,10 @@ export function DialogProjectCopyName(props: { onConfirm: (name: string) => void
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
Name project copy
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
@@ -61,17 +61,18 @@ export function DialogProjectCopyName(props: { onConfirm: (name: string) => void
|
||||
}}
|
||||
onSubmit={confirm}
|
||||
placeholder="Project copy name"
|
||||
placeholderColor={theme.textMuted}
|
||||
textColor={theme.text}
|
||||
focusedTextColor={theme.text}
|
||||
cursorColor={theme.text}
|
||||
placeholderColor={themeV2.text.subdued()}
|
||||
textColor={themeV2.text.formfield()}
|
||||
focusedTextColor={themeV2.text.formfield()}
|
||||
cursorColor={themeV2.text.formfield()}
|
||||
/>
|
||||
<box paddingBottom={1} flexDirection="row" gap={2}>
|
||||
<text fg={theme.text}>
|
||||
enter <span style={{ fg: theme.textMuted }}>submit</span>
|
||||
<text fg={themeV2.text()}>
|
||||
enter <span style={{ fg: themeV2.text.subdued() }}>submit</span>
|
||||
</text>
|
||||
<text fg={theme.text}>
|
||||
{shortcuts.get("dialog.project_copy.generate")} <span style={{ fg: theme.textMuted }}>generate one</span>
|
||||
<text fg={themeV2.text()}>
|
||||
{shortcuts.get("dialog.project_copy.generate")}{" "}
|
||||
<span style={{ fg: themeV2.text.subdued() }}>generate one</span>
|
||||
</text>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
@@ -8,47 +8,41 @@ export type DialogStatusProps = {}
|
||||
|
||||
export function DialogStatus() {
|
||||
const data = useData()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const dialog = useDialog()
|
||||
|
||||
const mcp = createMemo(() => data.location.mcp.server.list() ?? [])
|
||||
const color = (status: string) => {
|
||||
if (status === "connected") return themeV2.text.feedback.success()
|
||||
if (status === "failed") return themeV2.text.feedback.error()
|
||||
if (status === "needs_auth") return themeV2.text.feedback.warning()
|
||||
if (status === "needs_client_registration") return themeV2.text.feedback.error()
|
||||
return themeV2.text.subdued()
|
||||
}
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1} paddingBottom={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
||||
<text fg={themeV2.text()} attributes={TextAttributes.BOLD}>
|
||||
Status
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<Show when={mcp().length > 0} fallback={<text fg={theme.text}>No MCP servers</text>}>
|
||||
<Show when={mcp().length > 0} fallback={<text fg={themeV2.text()}>No MCP servers</text>}>
|
||||
<box>
|
||||
<text fg={theme.text}>
|
||||
<text fg={themeV2.text()}>
|
||||
{mcp().length} MCP server{mcp().length === 1 ? "" : "s"}
|
||||
</text>
|
||||
<For each={mcp()}>
|
||||
{(item) => (
|
||||
<box flexDirection="row" gap={1}>
|
||||
<text
|
||||
flexShrink={0}
|
||||
style={{
|
||||
fg: (
|
||||
{
|
||||
connected: theme.success,
|
||||
failed: theme.error,
|
||||
disabled: theme.textMuted,
|
||||
needs_auth: theme.warning,
|
||||
needs_client_registration: theme.error,
|
||||
} as Record<string, typeof theme.success>
|
||||
)[item.status.status],
|
||||
}}
|
||||
>
|
||||
<text flexShrink={0} style={{ fg: color(item.status.status) }}>
|
||||
•
|
||||
</text>
|
||||
<text fg={theme.text} wrapMode="word">
|
||||
<text fg={themeV2.text()} wrapMode="word">
|
||||
<b>{item.name}</b>{" "}
|
||||
<span style={{ fg: theme.textMuted }}>
|
||||
<span style={{ fg: themeV2.text.subdued() }}>
|
||||
<Switch fallback={item.status.status}>
|
||||
<Match when={item.status.status === "connected"}>Connected</Match>
|
||||
<Match when={item.status.status === "failed" && item.status}>{(val) => val().error}</Match>
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
import { useTheme } from "../context/theme"
|
||||
|
||||
export function PluginRouteMissing(props: { id: string; name: string; onHome: () => void }) {
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme()
|
||||
|
||||
return (
|
||||
<box width="100%" height="100%" alignItems="center" justifyContent="center" flexDirection="column" gap={1}>
|
||||
<text fg={theme.warning}>
|
||||
<text fg={themeV2.text.feedback.warning()}>
|
||||
Unknown plugin route: {props.id}/{props.name}
|
||||
</text>
|
||||
<box onMouseUp={props.onHome} backgroundColor={theme.backgroundElement} paddingLeft={1} paddingRight={1}>
|
||||
<text fg={theme.text}>go home</text>
|
||||
<box
|
||||
onMouseUp={props.onHome}
|
||||
backgroundColor={themeV2.background.action("hovered")}
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
>
|
||||
<text fg={themeV2.text.action("hovered")}>go home</text>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useTheme } from "../context/theme"
|
||||
import { Spinner } from "./spinner"
|
||||
|
||||
export function Reconnecting() {
|
||||
const theme = useTheme().theme
|
||||
const { themeV2 } = useTheme()
|
||||
|
||||
return (
|
||||
<box
|
||||
@@ -12,12 +12,12 @@ export function Reconnecting() {
|
||||
right={0}
|
||||
bottom={0}
|
||||
left={0}
|
||||
backgroundColor={theme.background}
|
||||
backgroundColor={themeV2.background()}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
>
|
||||
<box width={62} maxWidth="90%" flexDirection="column" alignItems="center" gap={1}>
|
||||
<Spinner color={theme.textMuted}>Waiting for background service...</Spinner>
|
||||
<Spinner color={themeV2.text.subdued()}>Waiting for background service...</Spinner>
|
||||
</box>
|
||||
</box>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useTheme } from "../context/theme"
|
||||
import { Spinner } from "./spinner"
|
||||
|
||||
export function StartupLoading(props: { ready: () => boolean }) {
|
||||
const theme = useTheme().theme
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const [show, setShow] = createSignal(false)
|
||||
const text = createMemo(() => (props.ready() ? "Finishing startup..." : "Loading plugins..."))
|
||||
let wait: NodeJS.Timeout | undefined
|
||||
@@ -54,8 +54,8 @@ export function StartupLoading(props: { ready: () => boolean }) {
|
||||
return (
|
||||
<Show when={show()}>
|
||||
<box position="absolute" zIndex={5000} left={0} right={0} bottom={1} justifyContent="center" alignItems="center">
|
||||
<box backgroundColor={theme.backgroundPanel} paddingLeft={1} paddingRight={1}>
|
||||
<Spinner color={theme.textMuted}>{text()}</Spinner>
|
||||
<box backgroundColor={themeV2.background()} paddingLeft={1} paddingRight={1}>
|
||||
<Spinner color={themeV2.text.subdued()}>{text()}</Spinner>
|
||||
</box>
|
||||
</box>
|
||||
</Show>
|
||||
|
||||
@@ -11,7 +11,7 @@ export type DialogAlertProps = {
|
||||
|
||||
export function DialogAlert(props: DialogAlertProps) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
mode: "modal",
|
||||
@@ -30,27 +30,27 @@ export function DialogAlert(props: DialogAlertProps) {
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={theme.textMuted}>{props.message}</text>
|
||||
<text fg={themeV2.text.subdued()}>{props.message}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
||||
<box
|
||||
paddingLeft={3}
|
||||
paddingRight={3}
|
||||
backgroundColor={theme.primary}
|
||||
backgroundColor={themeV2.background.action("focused")}
|
||||
onMouseUp={() => {
|
||||
props.onConfirm?.()
|
||||
dialog.clear()
|
||||
}}
|
||||
>
|
||||
<text fg={theme.selectedListItemText}>ok</text>
|
||||
<text fg={themeV2.text.action("focused")}>ok</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
@@ -18,7 +18,7 @@ export type DialogConfirmResult = boolean | undefined
|
||||
|
||||
export function DialogConfirm(props: DialogConfirmProps) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const [store, setStore] = createStore({
|
||||
active: "confirm" as "confirm" | "cancel",
|
||||
})
|
||||
@@ -57,15 +57,15 @@ export function DialogConfirm(props: DialogConfirmProps) {
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
{props.title}
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={theme.textMuted}>{props.message}</text>
|
||||
<text fg={themeV2.text.subdued()}>{props.message}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
||||
<For each={["cancel", "confirm"] as const}>
|
||||
@@ -73,14 +73,14 @@ export function DialogConfirm(props: DialogConfirmProps) {
|
||||
<box
|
||||
paddingLeft={1}
|
||||
paddingRight={1}
|
||||
backgroundColor={key === store.active ? theme.primary : undefined}
|
||||
backgroundColor={key === store.active ? themeV2.background.action("focused") : undefined}
|
||||
onMouseUp={() => {
|
||||
if (key === "confirm") props.onConfirm?.()
|
||||
if (key === "cancel") props.onCancel?.()
|
||||
dialog.clear()
|
||||
}}
|
||||
>
|
||||
<text fg={key === store.active ? theme.selectedListItemText : theme.textMuted}>
|
||||
<text fg={key === store.active ? themeV2.text.action("focused") : themeV2.text.subdued()}>
|
||||
{Locale.titlecase(key === "cancel" ? (props.label ?? key) : key)}
|
||||
</text>
|
||||
</box>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDialog, type DialogContext } from "./dialog"
|
||||
|
||||
export function DialogExportResult(props: { path: string; onClose?: () => void }) {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
|
||||
const close = () => {
|
||||
props.onClose?.()
|
||||
@@ -27,19 +27,19 @@ export function DialogExportResult(props: { path: string; onClose?: () => void }
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
Session exported
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={close}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={close}>
|
||||
esc
|
||||
</text>
|
||||
</box>
|
||||
<box>
|
||||
<text fg={theme.text}>{props.path}</text>
|
||||
<text fg={themeV2.text()}>{props.path}</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="flex-end" gap={1} paddingBottom={1}>
|
||||
<box paddingLeft={3} paddingRight={3} backgroundColor={theme.primary} onMouseUp={close}>
|
||||
<text fg={theme.selectedListItemText}>Close</text>
|
||||
<box paddingLeft={3} paddingRight={3} backgroundColor={themeV2.background.action("focused")} onMouseUp={close}>
|
||||
<text fg={themeV2.text.action("focused")}>Close</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useDialog } from "./dialog"
|
||||
|
||||
export function DialogHelp() {
|
||||
const dialog = useDialog()
|
||||
const { theme } = useTheme()
|
||||
const { themeV2 } = useTheme().contextual("elevated")
|
||||
const shortcuts = Keymap.useShortcuts()
|
||||
|
||||
Keymap.createLayer(() => ({
|
||||
@@ -19,21 +19,26 @@ export function DialogHelp() {
|
||||
return (
|
||||
<box paddingLeft={2} paddingRight={2} gap={1}>
|
||||
<box flexDirection="row" justifyContent="space-between">
|
||||
<text attributes={TextAttributes.BOLD} fg={theme.text}>
|
||||
<text attributes={TextAttributes.BOLD} fg={themeV2.text()}>
|
||||
Help
|
||||
</text>
|
||||
<text fg={theme.textMuted} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={themeV2.text.subdued()} onMouseUp={() => dialog.clear()}>
|
||||
esc/enter
|
||||
</text>
|
||||
</box>
|
||||
<box paddingBottom={1}>
|
||||
<text fg={theme.textMuted}>
|
||||
<text fg={themeV2.text.subdued()}>
|
||||
Press {shortcuts.get("command.palette.show")} to see all available actions and commands in any context.
|
||||
</text>
|
||||
</box>
|
||||
<box flexDirection="row" justifyContent="flex-end" paddingBottom={1}>
|
||||
<box paddingLeft={3} paddingRight={3} backgroundColor={theme.primary} onMouseUp={() => dialog.clear()}>
|
||||
<text fg={theme.selectedListItemText}>ok</text>
|
||||
<box
|
||||
paddingLeft={3}
|
||||
paddingRight={3}
|
||||
backgroundColor={themeV2.background.action("focused")}
|
||||
onMouseUp={() => dialog.clear()}
|
||||
>
|
||||
<text fg={themeV2.text.action("focused")}>ok</text>
|
||||
</box>
|
||||
</box>
|
||||
</box>
|
||||
|
||||
Reference in New Issue
Block a user