summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/app/src/components/dialog-select-server.tsx44
-rw-r--r--packages/ui/src/hooks/use-filtered-list.tsx15
2 files changed, 42 insertions, 17 deletions
diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx
index 7e2bcc181..3e4ee8883 100644
--- a/packages/app/src/components/dialog-select-server.tsx
+++ b/packages/app/src/components/dialog-select-server.tsx
@@ -5,6 +5,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
import { List } from "@opencode-ai/ui/list"
import { TextField } from "@opencode-ai/ui/text-field"
import { Button } from "@opencode-ai/ui/button"
+import { IconButton } from "@opencode-ai/ui/icon-button"
import { normalizeServerUrl, serverDisplayName, useServer } from "@/context/server"
import { usePlatform } from "@/context/platform"
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client"
@@ -116,6 +117,10 @@ export function DialogSelectServer() {
select(value, true)
}
+ async function handleRemove(url: string) {
+ server.remove(url)
+ }
+
return (
<Dialog title="Servers" description="Switch which OpenCode server this app connects to.">
<div class="flex flex-col gap-4 pb-4">
@@ -130,20 +135,33 @@ export function DialogSelectServer() {
}}
>
{(i) => (
- <div
- class="flex items-center gap-2 min-w-0 flex-1"
- classList={{ "opacity-50": store.status[i]?.healthy === false }}
- >
+ <div class="flex items-center gap-2 min-w-0 flex-1 group/item">
<div
- classList={{
- "size-1.5 rounded-full shrink-0": true,
- "bg-icon-success-base": store.status[i]?.healthy === true,
- "bg-icon-critical-base": store.status[i]?.healthy === false,
- "bg-border-weak-base": store.status[i] === undefined,
- }}
- />
- <span class="truncate">{serverDisplayName(i)}</span>
- <span class="text-text-weak">{store.status[i]?.version}</span>
+ class="flex items-center gap-2 min-w-0 flex-1"
+ classList={{ "opacity-50": store.status[i]?.healthy === false }}
+ >
+ <div
+ classList={{
+ "size-1.5 rounded-full shrink-0": true,
+ "bg-icon-success-base": store.status[i]?.healthy === true,
+ "bg-icon-critical-base": store.status[i]?.healthy === false,
+ "bg-border-weak-base": store.status[i] === undefined,
+ }}
+ />
+ <span class="truncate">{serverDisplayName(i)}</span>
+ <span class="text-text-weak">{store.status[i]?.version}</span>
+ </div>
+ <Show when={current() !== i && server.list.includes(i)}>
+ <IconButton
+ icon="circle-x"
+ variant="ghost"
+ class="bg-transparent transition-opacity shrink-0 hover:scale-110"
+ onClick={(e) => {
+ e.stopPropagation()
+ handleRemove(i)
+ }}
+ />
+ </Show>
</div>
)}
</List>
diff --git a/packages/ui/src/hooks/use-filtered-list.tsx b/packages/ui/src/hooks/use-filtered-list.tsx
index 1b3be4b4c..11bc35548 100644
--- a/packages/ui/src/hooks/use-filtered-list.tsx
+++ b/packages/ui/src/hooks/use-filtered-list.tsx
@@ -22,10 +22,17 @@ export function useFilteredList<T>(props: FilteredListProps<T>) {
const empty: Group[] = []
const [grouped, { refetch }] = createResource(
- () => ({
- filter: store.filter,
- items: typeof props.items === "function" ? undefined : props.items,
- }),
+ () => {
+ // When items is a function (not async filter function), call it to track changes
+ const itemsValue = typeof props.items === "function"
+ ? (props.items as () => T[])() // Call synchronous function to track it
+ : props.items
+
+ return {
+ filter: store.filter,
+ items: itemsValue,
+ }
+ },
async ({ filter, items }) => {
const needle = filter?.toLowerCase()
const all = (items ?? (await (props.items as (filter: string) => T[] | Promise<T[]>)(needle))) || []