Adam 2 недель назад
Родитель
Сommit
5be1202eea
1 измененных файлов с 48 добавлено и 27 удалено
  1. 48 27
      packages/app/src/components/session/session-header.tsx

+ 48 - 27
packages/app/src/components/session/session-header.tsx

@@ -112,21 +112,35 @@ export function SessionHeader() {
 
 
   const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({ finder: true })
   const [exists, setExists] = createStore<Partial<Record<OpenApp, boolean>>>({ finder: true })
 
 
+  const apps = createMemo(() => {
+    if (os() === "macos") return MAC_APPS
+    if (os() === "windows") return WINDOWS_APPS
+    return LINUX_APPS
+  })
+
+  const fileManager = createMemo(() => {
+    if (os() === "macos") return { label: "Finder", icon: "finder" as const }
+    if (os() === "windows") return { label: "File Explorer", icon: "file-explorer" as const }
+    return { label: "File Manager", icon: "finder" as const }
+  })
+
   createEffect(() => {
   createEffect(() => {
     if (platform.platform !== "desktop") return
     if (platform.platform !== "desktop") return
     if (!platform.checkAppExists) return
     if (!platform.checkAppExists) return
 
 
-    const list = os()
-    const apps = list === "macos" ? MAC_APPS : list === "windows" ? WINDOWS_APPS : list === "linux" ? LINUX_APPS : []
-    if (apps.length === 0) return
+    const list = apps()
+
+    setExists(Object.fromEntries(list.map((app) => [app.id, undefined])) as Partial<Record<OpenApp, boolean>>)
 
 
     void Promise.all(
     void Promise.all(
-      apps.map((app) =>
-        Promise.resolve(platform.checkAppExists?.(app.openWith)).then((value) => {
-          const ok = Boolean(value)
-          console.debug(`[session-header] App "${app.label}" (${app.openWith}): ${ok ? "exists" : "does not exist"}`)
-          return [app.id, ok] as const
-        }),
+      list.map((app) =>
+        Promise.resolve(platform.checkAppExists?.(app.openWith))
+          .then((value) => Boolean(value))
+          .catch(() => false)
+          .then((ok) => {
+            console.debug(`[session-header] App "${app.label}" (${app.openWith}): ${ok ? "exists" : "does not exist"}`)
+            return [app.id, ok] as const
+          }),
       ),
       ),
     ).then((entries) => {
     ).then((entries) => {
       setExists(Object.fromEntries(entries) as Partial<Record<OpenApp, boolean>>)
       setExists(Object.fromEntries(entries) as Partial<Record<OpenApp, boolean>>)
@@ -134,23 +148,23 @@ export function SessionHeader() {
   })
   })
 
 
   const options = createMemo(() => {
   const options = createMemo(() => {
-    if (os() === "macos") {
-      return [{ id: "finder", label: "Finder", icon: "finder" }, ...MAC_APPS.filter((app) => exists[app.id])] as const
-    }
-
-    if (os() === "windows") {
-      return [
-        { id: "finder", label: "File Explorer", icon: "file-explorer" },
-        ...WINDOWS_APPS.filter((app) => exists[app.id]),
-      ] as const
-    }
-
     return [
     return [
-      { id: "finder", label: "File Manager", icon: "finder" },
-      ...LINUX_APPS.filter((app) => exists[app.id]),
+      { id: "finder", label: fileManager().label, icon: fileManager().icon },
+      ...apps().filter((app) => exists[app.id]),
     ] as const
     ] as const
   })
   })
 
 
+  type OpenIcon = OpenApp | "file-explorer"
+  const base = new Set<OpenIcon>(["finder", "vscode", "cursor", "zed"])
+  const size = (id: OpenIcon) => (base.has(id) ? "size-4" : "size-[19px]")
+
+  const checksReady = createMemo(() => {
+    if (platform.platform !== "desktop") return true
+    if (!platform.checkAppExists) return true
+    const list = apps()
+    return list.every((app) => exists[app.id] !== undefined)
+  })
+
   const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
   const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
 
 
   const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
   const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
@@ -158,6 +172,7 @@ export function SessionHeader() {
 
 
   createEffect(() => {
   createEffect(() => {
     if (platform.platform !== "desktop") return
     if (platform.platform !== "desktop") return
+    if (!checksReady()) return
     const value = prefs.app
     const value = prefs.app
     if (options().some((o) => o.id === value)) return
     if (options().some((o) => o.id === value)) return
     setPrefs("app", options()[0]?.id ?? "finder")
     setPrefs("app", options()[0]?.id ?? "finder")
@@ -334,11 +349,13 @@ export function SessionHeader() {
                           onClick={() => openDir(current().id)}
                           onClick={() => openDir(current().id)}
                           aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
                           aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
                         >
                         >
-                          <AppIcon id={current().icon} class="size-4" />
+                          <div class="flex size-5 shrink-0 items-center justify-center">
+                            <AppIcon id={current().icon} class="size-4" />
+                          </div>
                           <span class="text-12-regular text-text-strong">Open</span>
                           <span class="text-12-regular text-text-strong">Open</span>
                         </Button>
                         </Button>
                         <div class="self-stretch w-px bg-border-base/70" />
                         <div class="self-stretch w-px bg-border-base/70" />
-                        <DropdownMenu>
+                        <DropdownMenu gutter={6} placement="bottom-end">
                           <DropdownMenu.Trigger
                           <DropdownMenu.Trigger
                             as={IconButton}
                             as={IconButton}
                             icon="chevron-down"
                             icon="chevron-down"
@@ -347,7 +364,7 @@ export function SessionHeader() {
                             aria-label={language.t("session.header.open.menu")}
                             aria-label={language.t("session.header.open.menu")}
                           />
                           />
                           <DropdownMenu.Portal>
                           <DropdownMenu.Portal>
-                            <DropdownMenu.Content placement="bottom-end" gutter={6}>
+                            <DropdownMenu.Content>
                               <DropdownMenu.Group>
                               <DropdownMenu.Group>
                                 <DropdownMenu.GroupLabel>{language.t("session.header.openIn")}</DropdownMenu.GroupLabel>
                                 <DropdownMenu.GroupLabel>{language.t("session.header.openIn")}</DropdownMenu.GroupLabel>
                                 <DropdownMenu.RadioGroup
                                 <DropdownMenu.RadioGroup
@@ -359,7 +376,9 @@ export function SessionHeader() {
                                 >
                                 >
                                   {options().map((o) => (
                                   {options().map((o) => (
                                     <DropdownMenu.RadioItem value={o.id} onSelect={() => openDir(o.id)}>
                                     <DropdownMenu.RadioItem value={o.id} onSelect={() => openDir(o.id)}>
-                                      <AppIcon id={o.icon} class="size-5" />
+                                      <div class="flex size-5 shrink-0 items-center justify-center">
+                                        <AppIcon id={o.icon} class={size(o.icon)} />
+                                      </div>
                                       <DropdownMenu.ItemLabel>{o.label}</DropdownMenu.ItemLabel>
                                       <DropdownMenu.ItemLabel>{o.label}</DropdownMenu.ItemLabel>
                                       <DropdownMenu.ItemIndicator>
                                       <DropdownMenu.ItemIndicator>
                                         <Icon name="check-small" size="small" class="text-icon-weak" />
                                         <Icon name="check-small" size="small" class="text-icon-weak" />
@@ -370,7 +389,9 @@ export function SessionHeader() {
                               </DropdownMenu.Group>
                               </DropdownMenu.Group>
                               <DropdownMenu.Separator />
                               <DropdownMenu.Separator />
                               <DropdownMenu.Item onSelect={copyPath}>
                               <DropdownMenu.Item onSelect={copyPath}>
-                                <Icon name="copy" size="small" class="text-icon-weak" />
+                                <div class="flex size-5 shrink-0 items-center justify-center">
+                                  <Icon name="copy" size="small" class="text-icon-weak" />
+                                </div>
                                 <DropdownMenu.ItemLabel>
                                 <DropdownMenu.ItemLabel>
                                   {language.t("session.header.open.copyPath")}
                                   {language.t("session.header.open.copyPath")}
                                 </DropdownMenu.ItemLabel>
                                 </DropdownMenu.ItemLabel>