fix dialog root complexity

This commit is contained in:
Dax Raad 2025-12-15 17:13:00 -05:00
parent f3e64cfb19
commit 56452d886d
4 changed files with 139 additions and 105 deletions

View file

@ -12,7 +12,7 @@ import { GlobalSDKProvider } from "@/context/global-sdk"
import { TerminalProvider } from "@/context/terminal"
import { PromptProvider } from "@/context/prompt"
import { NotificationProvider } from "@/context/notification"
import { DialogProvider, DialogRoot } from "@opencode-ai/ui/context/dialog"
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
import { CommandProvider } from "@/context/command"
import Layout from "@/pages/layout"
import Home from "@/pages/home"
@ -36,46 +36,46 @@ const url =
export function App() {
return (
<MarkedProvider>
<DiffComponentProvider component={Diff}>
<GlobalSDKProvider url={url}>
<GlobalSyncProvider>
<LayoutProvider>
<NotificationProvider>
<MetaProvider>
<Font />
<Router
root={(props) => (
<DialogProvider>
<DialogProvider>
<MarkedProvider>
<DiffComponentProvider component={Diff}>
<GlobalSDKProvider url={url}>
<GlobalSyncProvider>
<LayoutProvider>
<NotificationProvider>
<MetaProvider>
<Font />
<Router
root={(props) => (
<CommandProvider>
<Layout>{props.children}</Layout>
</CommandProvider>
</DialogProvider>
)}
>
<Route path="/" component={Home} />
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={(p) => (
<Show when={p.params.id || true} keyed>
<TerminalProvider>
<PromptProvider>
<Session />
</PromptProvider>
</TerminalProvider>
</Show>
)}
/>
</Route>
</Router>
</MetaProvider>
</NotificationProvider>
</LayoutProvider>
</GlobalSyncProvider>
</GlobalSDKProvider>
</DiffComponentProvider>
</MarkedProvider>
)}
>
<Route path="/" component={Home} />
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
path="/session/:id?"
component={(p) => (
<Show when={p.params.id || true} keyed>
<TerminalProvider>
<PromptProvider>
<Session />
</PromptProvider>
</TerminalProvider>
</Show>
)}
/>
</Route>
</Router>
</MetaProvider>
</NotificationProvider>
</LayoutProvider>
</GlobalSyncProvider>
</GlobalSDKProvider>
</DiffComponentProvider>
</MarkedProvider>
</DialogProvider>
)
}

View file

@ -6,8 +6,8 @@ import { createMemo } from "solid-js"
export const popularProviders = ["opencode", "anthropic", "github-copilot", "openai", "google", "openrouter", "vercel"]
export function useProviders() {
const params = useParams()
const globalSync = useGlobalSync()
const params = useParams()
const currentDirectory = createMemo(() => base64Decode(params.dir ?? ""))
const providers = createMemo(() => {
if (currentDirectory()) {

View file

@ -6,7 +6,6 @@ import { LocalProvider } from "@/context/local"
import { base64Decode } from "@opencode-ai/util/encode"
import { DataProvider } from "@opencode-ai/ui/context"
import { iife } from "@opencode-ai/util/iife"
import { DialogRoot } from "@opencode-ai/ui/context/dialog"
export default function Layout(props: ParentProps) {
const params = useParams()
@ -21,9 +20,7 @@ export default function Layout(props: ParentProps) {
const sync = useSync()
return (
<DataProvider data={sync.data} directory={directory()}>
<LocalProvider>
<DialogRoot>{props.children}</DialogRoot>
</LocalProvider>
<LocalProvider>{props.children}</LocalProvider>
</DataProvider>
)
})}

View file

@ -1,79 +1,116 @@
import { For, Show, type JSX } from "solid-js"
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
type DialogElement = JSX.Element | (() => JSX.Element)
export const { use: useDialog, provider: DialogProvider } = createSimpleContext({
name: "Dialog",
init: () => {
const [store, setStore] = createStore({
stack: [] as {
element: DialogElement
onClose?: () => void
}[],
})
return {
get stack() {
return store.stack
},
push(element: DialogElement, onClose?: () => void) {
setStore("stack", (s) => [...s, { element, onClose }])
},
pop() {
const current = store.stack.at(-1)
current?.onClose?.()
setStore("stack", store.stack.slice(0, -1))
},
replace(element: DialogElement, onClose?: () => void) {
for (const item of store.stack) {
item.onClose?.()
}
setStore("stack", [{ element, onClose }])
},
clear() {
for (const item of store.stack) {
item.onClose?.()
}
setStore("stack", [])
},
}
},
})
import {
createContext,
createMemo,
createSignal,
getOwner,
Owner,
ParentProps,
runWithOwner,
Show,
useContext,
type JSX,
} from "solid-js"
import { Dialog as Kobalte } from "@kobalte/core/dialog"
export function DialogRoot(props: { children?: JSX.Element }) {
const dialog = useDialog()
type DialogElement = () => JSX.Element
const Context = createContext<ReturnType<typeof init>>()
function init() {
const [store, setStore] = createSignal<
{
element: DialogElement
onClose?: () => void
owner: Owner
}[]
>([])
return {
get stack() {
return store()
},
push(element: DialogElement, owner: Owner, onClose?: () => void) {
setStore((s) => [...s, { element, onClose, owner }])
},
pop() {
const current = store().at(-1)
current?.onClose?.()
setStore((stack) => stack.slice(0, -1))
},
replace(element: DialogElement, owner: Owner, onClose?: () => void) {
for (const item of store()) {
item.onClose?.()
}
setStore([{ element, onClose, owner }])
},
clear() {
for (const item of store()) {
item.onClose?.()
}
setStore([])
},
}
}
export function DialogProvider(props: ParentProps) {
const ctx = init()
const last = createMemo(() => ctx.stack.at(-1))
return (
<>
<Context.Provider value={ctx}>
{props.children}
<Show when={dialog.stack.length > 0}>
<div data-component="dialog-stack">
<For each={dialog.stack}>
{(item, index) => (
<Show when={index() === dialog.stack.length - 1}>
<div data-component="dialog-stack">
<Show when={last()}>
{(item) =>
runWithOwner(item().owner, () => {
return (
<Kobalte
modal
defaultOpen
onOpenChange={(open) => {
if (!open) {
item.onClose?.()
dialog.pop()
item().onClose?.()
ctx.pop()
}
}}
>
<Kobalte.Portal>
<Kobalte.Overlay data-component="dialog-overlay" />
{typeof item.element === "function" ? item.element() : item.element}
{item().element()}
</Kobalte.Portal>
</Kobalte>
</Show>
)}
</For>
</div>
</Show>
</>
)
})
}
</Show>
</div>
</Context.Provider>
)
}
export function useDialog() {
const ctx = useContext(Context)
const owner = getOwner()
if (!owner) {
throw new Error("useDialog must be used within a DialogProvider")
}
if (!ctx) {
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)
},
push(element: DialogElement, onClose?: () => void) {
ctx.push(element, owner, onClose)
},
pop() {
ctx.pop()
},
clear() {
ctx.clear()
},
}
}