Quellcode durchsuchen

fix(app): preserve provider dialog backdrop (#35370)

Co-authored-by: Jay <53023+jayair@users.noreply.github.com>
Co-authored-by: Brendan Allan <git@brendonovich.dev>
opencode-agent[bot] vor 1 Monat
Ursprung
Commit
14df88eab5

+ 9 - 17
packages/app/src/components/dialog-connect-provider.tsx

@@ -17,19 +17,17 @@ import { useServerSync } from "@/context/server-sync"
 import { useLanguage } from "@/context/language"
 import { useProviders } from "@/hooks/use-providers"
 
-export function DialogConnectProvider(props: { provider: string; directory?: Accessor<string | undefined> }) {
+export function DialogConnectProvider(props: {
+  provider: string
+  directory?: Accessor<string | undefined>
+  onBack: () => void
+}) {
   const dialog = useDialog()
   const serverSync = useServerSync()
   const serverSDK = useServerSDK()
   const language = useLanguage()
   const providers = useProviders(props.directory)
 
-  const all = () => {
-    void import("./dialog-select-provider").then((x) => {
-      dialog.show(() => <x.DialogSelectProvider directory={props.directory} />)
-    })
-  }
-
   const alive = { value: true }
   const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
 
@@ -364,19 +362,11 @@ export function DialogConnectProvider(props: { provider: string; directory?: Acc
   }
 
   function goBack() {
-    if (methods().length === 1) {
-      all()
-      return
-    }
-    if (store.authorization) {
-      dispatch({ type: "method.reset" })
-      return
-    }
-    if (store.methodIndex !== undefined) {
+    if (methods().length > 1 && store.methodIndex !== undefined) {
       dispatch({ type: "method.reset" })
       return
     }
-    all()
+    props.onBack()
   }
 
   function MethodSelection() {
@@ -600,6 +590,7 @@ export function DialogConnectProvider(props: { provider: string; directory?: Acc
 
   return (
     <Dialog
+      class="h-full"
       title={
         <IconButton
           tabIndex={-1}
@@ -609,6 +600,7 @@ export function DialogConnectProvider(props: { provider: string; directory?: Acc
           aria-label={language.t("common.goBack")}
         />
       }
+      transition
     >
       <div class="flex flex-col gap-6 px-2.5 pb-3">
         <div class="px-2.5 flex gap-4 items-center">

+ 4 - 13
packages/app/src/components/dialog-custom-provider.tsx

@@ -6,18 +6,16 @@ import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
 import { useMutation } from "@tanstack/solid-query"
 import { TextField } from "@opencode-ai/ui/text-field"
 import { showToast } from "@/utils/toast"
-import { type Accessor, batch, For } from "solid-js"
+import { batch, For } from "solid-js"
 import { createStore, produce } from "solid-js/store"
 import { Link } from "@/components/link"
 import { useServerSDK } from "@/context/server-sdk"
 import { useServerSync } from "@/context/server-sync"
 import { useLanguage } from "@/context/language"
 import { type FormState, headerRow, modelRow, validateCustomProvider } from "./dialog-custom-provider-form"
-import { DialogSelectProvider } from "./dialog-select-provider"
 
 type Props = {
-  back?: "providers" | "close"
-  directory?: Accessor<string | undefined>
+  onBack: () => void
 }
 
 export function DialogCustomProvider(props: Props) {
@@ -36,14 +34,6 @@ export function DialogCustomProvider(props: Props) {
     err: {},
   })
 
-  const goBack = () => {
-    if (props.back === "close") {
-      dialog.close()
-      return
-    }
-    dialog.show(() => <DialogSelectProvider directory={props.directory} />)
-  }
-
   const addModel = () => {
     setForm(
       "models",
@@ -164,12 +154,13 @@ export function DialogCustomProvider(props: Props) {
 
   return (
     <Dialog
+      class="h-full"
       title={
         <IconButton
           tabIndex={-1}
           icon="arrow-left"
           variant="ghost"
-          onClick={goBack}
+          onClick={props.onBack}
           aria-label={language.t("common.goBack")}
         />
       }

+ 1 - 1
packages/app/src/components/dialog-select-model-unpaid.tsx

@@ -24,7 +24,7 @@ export const DialogSelectModelUnpaid: Component<{ model?: ModelState }> = (props
 
   const connect = (provider: string) => {
     void import("./dialog-connect-provider").then((x) => {
-      dialog.show(() => <x.DialogConnectProvider provider={provider} directory={directory} />)
+      dialog.show(() => <x.DialogConnectProvider provider={provider} directory={directory} onBack={all} />)
     })
   }
 

+ 69 - 59
packages/app/src/components/dialog-select-provider.tsx

@@ -1,5 +1,5 @@
-import { type Accessor, Component, Show } from "solid-js"
-import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { type Accessor, Component, Match, Show, Switch } from "solid-js"
+import { createStore } from "solid-js/store"
 import { popularProviders, useProviders } from "@/hooks/use-providers"
 import { Dialog } from "@opencode-ai/ui/dialog"
 import { List } from "@opencode-ai/ui/list"
@@ -12,9 +12,13 @@ import { DialogCustomProvider } from "./dialog-custom-provider"
 const CUSTOM_ID = "_custom"
 
 export const DialogSelectProvider: Component<{ directory?: Accessor<string | undefined> }> = (props) => {
-  const dialog = useDialog()
   const providers = useProviders(props.directory)
   const language = useLanguage()
+  const [store, setStore] = createStore({ selected: undefined as string | undefined })
+
+  function showPicker() {
+    setStore("selected", undefined)
+  }
 
   const popularGroup = () => language.t("dialog.provider.group.popular")
   const otherGroup = () => language.t("dialog.provider.group.other")
@@ -27,61 +31,67 @@ export const DialogSelectProvider: Component<{ directory?: Accessor<string | und
   }
 
   return (
-    <Dialog title={language.t("command.provider.connect")} transition>
-      <List
-        class="px-3"
-        search={{ placeholder: language.t("dialog.provider.search.placeholder"), autofocus: true }}
-        emptyMessage={language.t("dialog.provider.empty")}
-        activeIcon="plus-small"
-        key={(x) => x?.id}
-        items={() => {
-          language.locale()
-          return [{ id: CUSTOM_ID, name: customLabel() }, ...providers.all().values()]
-        }}
-        filterKeys={["id", "name"]}
-        groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())}
-        sortBy={(a, b) => {
-          if (a.id === CUSTOM_ID) return -1
-          if (b.id === CUSTOM_ID) return 1
-          if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
-            return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
-          return a.name.localeCompare(b.name)
-        }}
-        sortGroupsBy={(a, b) => {
-          const popular = popularGroup()
-          if (a.category === popular && b.category !== popular) return -1
-          if (b.category === popular && a.category !== popular) return 1
-          return 0
-        }}
-        onSelect={(x) => {
-          if (!x) return
-          if (x.id === CUSTOM_ID) {
-            dialog.show(() => <DialogCustomProvider back="providers" directory={props.directory} />)
-            return
-          }
-          dialog.show(() => <DialogConnectProvider provider={x.id} directory={props.directory} />)
-        }}
-      >
-        {(i) => (
-          <div class="px-1.25 w-full flex items-center gap-x-3">
-            <ProviderIcon data-slot="list-item-extra-icon" id={i.id} />
-            <span>{i.name}</span>
-            <Show when={i.id === "opencode"}>
-              <div class="text-14-regular text-text-weak">{language.t("dialog.provider.opencode.tagline")}</div>
-            </Show>
-            <Show when={i.id === CUSTOM_ID}>
-              <Tag>{language.t("settings.providers.tag.custom")}</Tag>
-            </Show>
-            <Show when={i.id === "opencode"}>
-              <Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
-            </Show>
-            <Show when={note(i.id)}>{(value) => <div class="text-14-regular text-text-weak">{value()}</div>}</Show>
-            <Show when={i.id === "opencode-go"}>
-              <Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
-            </Show>
-          </div>
-        )}
-      </List>
-    </Dialog>
+    <Switch>
+      <Match when={store.selected === CUSTOM_ID}>
+        <DialogCustomProvider onBack={showPicker} />
+      </Match>
+      <Match when={store.selected && store.selected !== CUSTOM_ID ? store.selected : undefined}>
+        {(provider) => <DialogConnectProvider provider={provider()} directory={props.directory} onBack={showPicker} />}
+      </Match>
+      <Match when={true}>
+        <Dialog class="h-full" transition title={language.t("command.provider.connect")}>
+          <List
+            class="px-3"
+            search={{ placeholder: language.t("dialog.provider.search.placeholder"), autofocus: true }}
+            emptyMessage={language.t("dialog.provider.empty")}
+            activeIcon="plus-small"
+            key={(x) => x?.id}
+            items={() => {
+              language.locale()
+              return [{ id: CUSTOM_ID, name: customLabel() }, ...providers.all().values()]
+            }}
+            filterKeys={["id", "name"]}
+            groupBy={(x) => (popularProviders.includes(x.id) ? popularGroup() : otherGroup())}
+            sortBy={(a, b) => {
+              if (a.id === CUSTOM_ID) return -1
+              if (b.id === CUSTOM_ID) return 1
+              if (popularProviders.includes(a.id) && popularProviders.includes(b.id))
+                return popularProviders.indexOf(a.id) - popularProviders.indexOf(b.id)
+              return a.name.localeCompare(b.name)
+            }}
+            sortGroupsBy={(a, b) => {
+              const popular = popularGroup()
+              if (a.category === popular && b.category !== popular) return -1
+              if (b.category === popular && a.category !== popular) return 1
+              return 0
+            }}
+            onSelect={(x) => {
+              if (!x) return
+              setStore("selected", x.id)
+            }}
+          >
+            {(i) => (
+              <div class="px-1.25 w-full flex items-center gap-x-3">
+                <ProviderIcon data-slot="list-item-extra-icon" id={i.id} />
+                <span>{i.name}</span>
+                <Show when={i.id === "opencode"}>
+                  <div class="text-14-regular text-text-weak">{language.t("dialog.provider.opencode.tagline")}</div>
+                </Show>
+                <Show when={i.id === CUSTOM_ID}>
+                  <Tag>{language.t("settings.providers.tag.custom")}</Tag>
+                </Show>
+                <Show when={i.id === "opencode"}>
+                  <Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
+                </Show>
+                <Show when={note(i.id)}>{(value) => <div class="text-14-regular text-text-weak">{value()}</div>}</Show>
+                <Show when={i.id === "opencode-go"}>
+                  <Tag>{language.t("dialog.provider.tag.recommended")}</Tag>
+                </Show>
+              </div>
+            )}
+          </List>
+        </Dialog>
+      </Match>
+    </Switch>
   )
 }

+ 7 - 2
packages/app/src/components/settings-providers.tsx

@@ -211,7 +211,12 @@ const SettingsProvidersContent: Component = () => {
                     variant="secondary"
                     icon="plus-small"
                     onClick={() => {
-                      dialog.show(() => <DialogConnectProvider provider={item.id} />)
+                      dialog.show(() => (
+                        <DialogConnectProvider
+                          provider={item.id}
+                          onBack={() => dialog.show(() => <DialogSelectProvider />)}
+                        />
+                      ))
                     }}
                   >
                     {language.t("common.connect")}
@@ -239,7 +244,7 @@ const SettingsProvidersContent: Component = () => {
                 variant="secondary"
                 icon="plus-small"
                 onClick={() => {
-                  dialog.show(() => <DialogCustomProvider back="close" />)
+                  dialog.show(() => <DialogCustomProvider onBack={dialog.close} />)
                 }}
               >
                 {language.t("common.connect")}

+ 7 - 2
packages/app/src/components/settings-v2/providers.tsx

@@ -211,7 +211,12 @@ export const SettingsProvidersV2: Component = () => {
                     variant="neutral"
                     icon="plus"
                     onClick={() => {
-                      dialog.show(() => <DialogConnectProvider provider={item.id} />)
+                      dialog.show(() => (
+                        <DialogConnectProvider
+                          provider={item.id}
+                          onBack={() => dialog.show(() => <DialogSelectProvider />)}
+                        />
+                      ))
                     }}
                   >
                     {language.t("common.connect")}
@@ -241,7 +246,7 @@ export const SettingsProvidersV2: Component = () => {
                 variant="neutral"
                 icon="plus"
                 onClick={() => {
-                  dialog.show(() => <DialogCustomProvider back="close" />)
+                  dialog.show(() => <DialogCustomProvider onBack={dialog.close} />)
                 }}
               >
                 {language.t("common.connect")}

+ 10 - 1
packages/app/src/pages/session/usage-exceeded-dialogs.tsx

@@ -77,7 +77,16 @@ export function useUsageExceededDialogs() {
               if (dontShowAgain) setGoUpsellState(keys.dontShow, Date.now())
               else {
                 void import("../../components/dialog-connect-provider").then((x) =>
-                  dialog.show(() => <x.DialogConnectProvider provider="opencode-go" />),
+                  dialog.show(() => (
+                    <x.DialogConnectProvider
+                      provider="opencode-go"
+                      onBack={() => {
+                        void import("../../components/dialog-select-provider").then((provider) => {
+                          dialog.show(() => <provider.DialogSelectProvider />)
+                        })
+                      }}
+                    />
+                  )),
                 )
               }
             }}