Browse Source

tui: refactor dialog system to use single active dialog instead of stack

Dax Raad 2 months ago
parent
commit
112c58abf5

+ 10 - 12
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()

+ 1 - 1
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) => (

+ 3 - 3
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

+ 3 - 3
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>

+ 1 - 1
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) => (

+ 1 - 3
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"}

+ 3 - 3
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)} />)
       }
     }
 

+ 1 - 1
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)

+ 1 - 1
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) {

+ 4 - 4
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>

+ 50 - 72
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()
     },
   }
 }