summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-12-15 19:33:17 -0500
committerDax Raad <[email protected]>2025-12-15 19:35:18 -0500
commit112c58abf516e4523fb698b868a710c40b271559 (patch)
treeba7f47f02cc5cda42e5ac0d5ae563be3e452ac68 /packages
parent0dce5173ccca05aa7bd9eaabab78fe1f5f4d4360 (diff)
downloadopencode-112c58abf516e4523fb698b868a710c40b271559.tar.gz
opencode-112c58abf516e4523fb698b868a710c40b271559.zip
tui: refactor dialog system to use single active dialog instead of stack
Diffstat (limited to 'packages')
-rw-r--r--packages/desktop/src/components/dialog-connect-provider.tsx22
-rw-r--r--packages/desktop/src/components/dialog-select-file.tsx2
-rw-r--r--packages/desktop/src/components/dialog-select-model-unpaid.tsx6
-rw-r--r--packages/desktop/src/components/dialog-select-model.tsx6
-rw-r--r--packages/desktop/src/components/dialog-select-provider.tsx2
-rw-r--r--packages/desktop/src/components/prompt-input.tsx4
-rw-r--r--packages/desktop/src/context/command.tsx6
-rw-r--r--packages/desktop/src/context/notification.tsx2
-rw-r--r--packages/desktop/src/pages/layout.tsx2
-rw-r--r--packages/desktop/src/pages/session.tsx8
-rw-r--r--packages/ui/src/context/dialog.tsx122
11 files changed, 78 insertions, 104 deletions
diff --git a/packages/desktop/src/components/dialog-connect-provider.tsx b/packages/desktop/src/components/dialog-connect-provider.tsx
index 4660e1398..0d6737815 100644
--- a/packages/desktop/src/components/dialog-connect-provider.tsx
+++ b/packages/desktop/src/components/dialog-connect-provider.tsx
@@ -108,20 +108,18 @@ export function DialogConnectProvider(props: { provider: string }) {
async function complete() {
await globalSDK.client.global.dispose()
- setTimeout(() => {
- showToast({
- variant: "success",
- icon: "circle-check",
- title: `${provider().name} connected`,
- description: `${provider().name} models are now available to use.`,
- })
- dialog.replace(() => <DialogSelectModel provider={props.provider} />)
- }, 1000)
+ dialog.close()
+ showToast({
+ variant: "success",
+ icon: "circle-check",
+ title: `${provider().name} connected`,
+ description: `${provider().name} models are now available to use.`,
+ })
}
function goBack() {
if (methods().length === 1) {
- dialog.replace(() => <DialogSelectProvider />)
+ dialog.show(() => <DialogSelectProvider />)
return
}
if (store.authorization) {
@@ -133,7 +131,7 @@ export function DialogConnectProvider(props: { provider: string }) {
setStore("methodIndex", undefined)
return
}
- dialog.replace(() => <DialogSelectProvider />)
+ dialog.show(() => <DialogSelectProvider />)
}
return (
@@ -352,7 +350,7 @@ export function DialogConnectProvider(props: { provider: string }) {
})
if (result.error) {
// TODO: show error
- dialog.clear()
+ dialog.close()
return
}
await complete()
diff --git a/packages/desktop/src/components/dialog-select-file.tsx b/packages/desktop/src/components/dialog-select-file.tsx
index b719e15d2..61c518719 100644
--- a/packages/desktop/src/components/dialog-select-file.tsx
+++ b/packages/desktop/src/components/dialog-select-file.tsx
@@ -27,7 +27,7 @@ export function DialogSelectFile() {
if (path) {
tabs().open("file://" + path)
}
- dialog.clear()
+ dialog.close()
}}
>
{(i) => (
diff --git a/packages/desktop/src/components/dialog-select-model-unpaid.tsx b/packages/desktop/src/components/dialog-select-model-unpaid.tsx
index 7cdb24915..77e493d3c 100644
--- a/packages/desktop/src/components/dialog-select-model-unpaid.tsx
+++ b/packages/desktop/src/components/dialog-select-model-unpaid.tsx
@@ -42,7 +42,7 @@ export const DialogSelectModelUnpaid: Component = () => {
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
})
- dialog.clear()
+ dialog.close()
}}
>
{(i) => (
@@ -75,7 +75,7 @@ export const DialogSelectModelUnpaid: Component = () => {
}}
onSelect={(x) => {
if (!x) return
- dialog.replace(() => <DialogConnectProvider provider={x.id} />)
+ dialog.show(() => <DialogConnectProvider provider={x.id} />)
}}
>
{(i) => (
@@ -105,7 +105,7 @@ export const DialogSelectModelUnpaid: Component = () => {
class="w-full justify-start px-[11px] py-3.5 gap-4.5 text-14-medium"
icon="dot-grid"
onClick={() => {
- dialog.replace(() => <DialogSelectProvider />)
+ dialog.show(() => <DialogSelectProvider />)
}}
>
View all providers
diff --git a/packages/desktop/src/components/dialog-select-model.tsx b/packages/desktop/src/components/dialog-select-model.tsx
index f0b2e6db9..622ab15fb 100644
--- a/packages/desktop/src/components/dialog-select-model.tsx
+++ b/packages/desktop/src/components/dialog-select-model.tsx
@@ -28,7 +28,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
class="h-7 -my-1 text-14-medium"
icon="plus-small"
tabIndex={-1}
- onClick={() => dialog.replace(() => <DialogSelectProvider />)}
+ onClick={() => dialog.show(() => <DialogSelectProvider />)}
>
Connect provider
</Button>
@@ -57,7 +57,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
local.model.set(x ? { modelID: x.id, providerID: x.provider.id } : undefined, {
recent: true,
})
- dialog.clear()
+ dialog.close()
}}
>
{(i) => (
@@ -75,7 +75,7 @@ export const DialogSelectModel: Component<{ provider?: string }> = (props) => {
<Button
variant="ghost"
class="ml-3 mt-5 mb-6 text-text-base self-start"
- onClick={() => dialog.replace(() => <DialogManageModels />)}
+ onClick={() => dialog.show(() => <DialogManageModels />)}
>
Manage models
</Button>
diff --git a/packages/desktop/src/components/dialog-select-provider.tsx b/packages/desktop/src/components/dialog-select-provider.tsx
index 8da10b1d5..52fac7073 100644
--- a/packages/desktop/src/components/dialog-select-provider.tsx
+++ b/packages/desktop/src/components/dialog-select-provider.tsx
@@ -34,7 +34,7 @@ export const DialogSelectProvider: Component = () => {
}}
onSelect={(x) => {
if (!x) return
- dialog.replace(() => <DialogConnectProvider provider={x.id} />)
+ dialog.show(() => <DialogConnectProvider provider={x.id} />)
}}
>
{(i) => (
diff --git a/packages/desktop/src/components/prompt-input.tsx b/packages/desktop/src/components/prompt-input.tsx
index f2821c3c7..6e147242d 100644
--- a/packages/desktop/src/components/prompt-input.tsx
+++ b/packages/desktop/src/components/prompt-input.tsx
@@ -864,9 +864,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
as="div"
variant="ghost"
onClick={() =>
- dialog.replace(() =>
- providers.paid().length > 0 ? <DialogSelectModel /> : <DialogSelectModelUnpaid />,
- )
+ dialog.show(() => (providers.paid().length > 0 ? <DialogSelectModel /> : <DialogSelectModelUnpaid />))
}
>
{local.model.current()?.name ?? "Select model"}
diff --git a/packages/desktop/src/context/command.tsx b/packages/desktop/src/context/command.tsx
index d4ef8e166..8fd76ee21 100644
--- a/packages/desktop/src/context/command.tsx
+++ b/packages/desktop/src/context/command.tsx
@@ -128,7 +128,7 @@ function DialogCommand(props: { options: CommandOption[] }) {
groupBy={(x) => x.category ?? ""}
onSelect={(option) => {
if (option) {
- dialog.clear()
+ dialog.close()
option.onSelect?.("palette")
}
}}
@@ -174,8 +174,8 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
const suspended = () => suspendCount() > 0
const showPalette = () => {
- if (dialog.stack.length === 0) {
- dialog.replace(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
+ if (!dialog.active) {
+ dialog.show(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
}
}
diff --git a/packages/desktop/src/context/notification.tsx b/packages/desktop/src/context/notification.tsx
index 045361630..ee15bc34a 100644
--- a/packages/desktop/src/context/notification.tsx
+++ b/packages/desktop/src/context/notification.tsx
@@ -59,7 +59,7 @@ export const { use: useNotification, provider: NotificationProvider } = createSi
time: Date.now(),
viewed: false,
}
- switch (event?.type) {
+ switch (event.type) {
case "session.idle": {
const sessionID = event.properties.sessionID
const [syncStore] = globalSync.child(directory)
diff --git a/packages/desktop/src/pages/layout.tsx b/packages/desktop/src/pages/layout.tsx
index 2f0c6e050..27852967a 100644
--- a/packages/desktop/src/pages/layout.tsx
+++ b/packages/desktop/src/pages/layout.tsx
@@ -217,7 +217,7 @@ export default function Layout(props: ParentProps) {
])
function connectProvider() {
- dialog.replace(() => <DialogSelectProvider />)
+ dialog.show(() => <DialogSelectProvider />)
}
function navigateToProject(directory: string | undefined) {
diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx
index 9167ef7b7..390872d36 100644
--- a/packages/desktop/src/pages/session.tsx
+++ b/packages/desktop/src/pages/session.tsx
@@ -176,7 +176,7 @@ export default function Page() {
category: "File",
keybind: "mod+p",
slash: "open",
- onSelect: () => dialog.replace(() => <DialogSelectFile />),
+ onSelect: () => dialog.show(() => <DialogSelectFile />),
},
// {
// id: "theme.toggle",
@@ -245,7 +245,7 @@ export default function Page() {
category: "Model",
keybind: "mod+'",
slash: "model",
- onSelect: () => dialog.replace(() => <DialogSelectModel />),
+ onSelect: () => dialog.show(() => <DialogSelectModel />),
},
{
id: "agent.cycle",
@@ -320,7 +320,7 @@ export default function Page() {
const handleKeyDown = (event: KeyboardEvent) => {
if ((document.activeElement as HTMLElement)?.dataset?.component === "terminal") return
- if (dialog.stack.length > 0) return
+ if (dialog.active) return
if (event.key === "PageUp" || event.key === "PageDown") {
const scrollContainer = document.querySelector('[data-slot="session-turn-content"]') as HTMLElement
@@ -613,7 +613,7 @@ export default function Page() {
icon="plus-small"
variant="ghost"
iconSize="large"
- onClick={() => dialog.replace(() => <DialogSelectFile />)}
+ onClick={() => dialog.show(() => <DialogSelectFile />)}
/>
</Tooltip>
</div>
diff --git a/packages/ui/src/context/dialog.tsx b/packages/ui/src/context/dialog.tsx
index b15d96991..71fc63806 100644
--- a/packages/ui/src/context/dialog.tsx
+++ b/packages/ui/src/context/dialog.tsx
@@ -1,9 +1,7 @@
import {
createContext,
createEffect,
- createMemo,
createSignal,
- For,
getOwner,
Owner,
ParentProps,
@@ -13,73 +11,58 @@ import {
type JSX,
} from "solid-js"
import { Dialog as Kobalte } from "@kobalte/core/dialog"
-import { iife } from "@opencode-ai/util/iife"
type DialogElement = () => JSX.Element
const Context = createContext<ReturnType<typeof init>>()
function init() {
- const [store, setStore] = createSignal<
- {
- id: string
- element: DialogElement
- onClose?: () => void
- owner: Owner
- }[]
- >([])
+ const [active, setActive] = createSignal<
+ | {
+ id: string
+ element: DialogElement
+ onClose?: () => void
+ owner: Owner
+ }
+ | undefined
+ >()
const result = {
- get stack() {
- return store()
- },
- pop() {
- const current = store().at(-1)
- if (!current) return
- current?.onClose?.()
- setStore((stack) => {
- stack.pop()
- return [...stack]
- })
+ get active() {
+ return active()
},
- replace(element: DialogElement, owner: Owner, onClose?: () => void) {
- for (const item of store()) {
- item.onClose?.()
- }
- const id = Math.random().toString(36)
- setStore([
- {
- id,
- element: () =>
- runWithOwner(owner, () => (
- <Show when={result.stack.at(-1)?.id === id}>
- <Kobalte
- modal
- defaultOpen
- onOpenChange={(open) => {
- if (!open) {
- onClose?.()
- result.pop()
- }
- }}
- >
- <Kobalte.Portal>
- <Kobalte.Overlay data-component="dialog-overlay" />
- {element()}
- </Kobalte.Portal>
- </Kobalte>
- </Show>
- )),
- onClose,
- owner,
- },
- ])
+ close() {
+ active()?.onClose?.()
+ setActive(undefined)
},
- clear() {
- for (const item of store()) {
- item.onClose?.()
- }
- setStore([])
+ show(element: DialogElement, owner: Owner, onClose?: () => void) {
+ active()?.onClose?.()
+ const id = Math.random().toString(36).slice(2)
+ setActive({
+ id,
+ element: () =>
+ runWithOwner(owner, () => (
+ <Show when={active()?.id === id}>
+ <Kobalte
+ modal
+ open={true}
+ onOpenChange={(open) => {
+ if (!open) {
+ console.log("closing")
+ result.close()
+ }
+ }}
+ >
+ <Kobalte.Portal>
+ <Kobalte.Overlay data-component="dialog-overlay" />
+ {element()}
+ </Kobalte.Portal>
+ </Kobalte>
+ </Show>
+ )),
+ onClose,
+ owner,
+ })
},
}
@@ -89,14 +72,12 @@ function init() {
export function DialogProvider(props: ParentProps) {
const ctx = init()
createEffect(() => {
- console.log("store", ctx.stack.length)
+ console.log("active", ctx.active)
})
return (
<Context.Provider value={ctx}>
{props.children}
- <div data-component="dialog-stack">
- <For each={ctx.stack}>{(item) => <>{item.element()}</>}</For>
- </div>
+ <div data-component="dialog-stack">{ctx.active?.element?.()}</div>
</Context.Provider>
)
}
@@ -111,17 +92,14 @@ export function useDialog() {
throw new Error("useDialog must be used within a DialogProvider")
}
return {
- get stack() {
- return ctx.stack
- },
- replace(element: DialogElement, onClose?: () => void) {
- ctx.replace(element, owner, onClose)
+ get active() {
+ return ctx.active
},
- pop() {
- ctx.pop()
+ show(element: DialogElement, onClose?: () => void) {
+ ctx.show(element, owner, onClose)
},
- clear() {
- ctx.clear()
+ close() {
+ ctx.close()
},
}
}