summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components/session
diff options
context:
space:
mode:
authorFilip <[email protected]>2026-02-11 11:40:52 +0100
committerGitHub <[email protected]>2026-02-11 04:40:52 -0600
commitcf7a1b8d80581ebee7a968602cec2e0561e08cdd (patch)
tree576ef572ee75de8c488300ff52241d98b0d3efc7 /packages/app/src/components/session
parent5ba4c0e024359dd1817bb5513982ee8cbfb24001 (diff)
downloadopencode-cf7a1b8d80581ebee7a968602cec2e0561e08cdd.tar.gz
opencode-cf7a1b8d80581ebee7a968602cec2e0561e08cdd.zip
feat(desktop): enhance Windows app resolution and UI loading states (#13084)
Diffstat (limited to 'packages/app/src/components/session')
-rw-r--r--packages/app/src/components/session/session-header.tsx59
1 files changed, 42 insertions, 17 deletions
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index 94b843666..3bf3c08e9 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -1,4 +1,4 @@
-import { createEffect, createMemo, onCleanup, Show } from "solid-js"
+import { createEffect, createMemo, createResource, onCleanup, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web"
import { useParams } from "@solidjs/router"
@@ -18,6 +18,7 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
import { Button } from "@opencode-ai/ui/button"
import { AppIcon } from "@opencode-ai/ui/app-icon"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
+import { Spinner } from "@opencode-ai/ui/spinner"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { Popover } from "@opencode-ai/ui/popover"
import { TextField } from "@opencode-ai/ui/text-field"
@@ -167,6 +168,7 @@ export function SessionHeader() {
const [prefs, setPrefs] = persisted(Persist.global("open.app"), createStore({ app: "finder" as OpenApp }))
const [menu, setMenu] = createStore({ open: false })
+ const [openRequest, setOpenRequest] = createStore({ app: undefined as OpenApp | undefined, version: 0 })
const canOpen = createMemo(() => platform.platform === "desktop" && !!platform.openPath && server.isLocal())
const current = createMemo(() => options().find((o) => o.id === prefs.app) ?? options()[0])
@@ -179,20 +181,32 @@ export function SessionHeader() {
setPrefs("app", options()[0]?.id ?? "finder")
})
- const openDir = (app: OpenApp) => {
- const directory = projectDirectory()
- if (!directory) return
- if (!canOpen()) return
-
- const item = options().find((o) => o.id === app)
- const openWith = item && "openWith" in item ? item.openWith : undefined
- Promise.resolve(platform.openPath?.(directory, openWith)).catch((err: unknown) => {
- showToast({
- variant: "error",
- title: language.t("common.requestFailed"),
- description: err instanceof Error ? err.message : String(err),
- })
+ const [openTask] = createResource(
+ () => openRequest.app && openRequest.version,
+ async () => {
+ const app = openRequest.app
+ const directory = projectDirectory()
+ if (!app || !directory || !canOpen()) return
+
+ const item = options().find((o) => o.id === app)
+ const openWith = item && "openWith" in item ? item.openWith : undefined
+ await platform.openPath?.(directory, openWith)
+ },
+ )
+
+ createEffect(() => {
+ const err = openTask.error
+ if (!err) return
+ showToast({
+ variant: "error",
+ title: language.t("common.requestFailed"),
+ description: err instanceof Error ? err.message : String(err),
})
+ })
+
+ const openDir = (app: OpenApp) => {
+ if (openTask.loading) return
+ setOpenRequest({ app, version: openRequest.version + 1 })
}
const copyPath = () => {
@@ -346,12 +360,18 @@ export function SessionHeader() {
<div class="flex h-[24px] box-border items-center rounded-md border border-border-base bg-surface-panel overflow-hidden">
<Button
variant="ghost"
- class="rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none"
+ class="rounded-none h-full py-0 pr-3 pl-2 gap-1.5 border-none shadow-none disabled:!cursor-default"
+ classList={{
+ "bg-surface-raised-base-active": openTask.loading,
+ }}
onClick={() => openDir(current().id)}
+ disabled={openTask.loading}
aria-label={language.t("session.header.open.ariaLabel", { app: current().label })}
>
<div class="flex size-5 shrink-0 items-center justify-center">
- <AppIcon id={current().icon} class="size-4" />
+ <Show when={openTask.loading} fallback={<AppIcon id={current().icon} class="size-4" />}>
+ <Spinner class="size-3.5 text-icon-base" />
+ </Show>
</div>
<span class="text-12-regular text-text-strong">Open</span>
</Button>
@@ -366,7 +386,11 @@ export function SessionHeader() {
as={IconButton}
icon="chevron-down"
variant="ghost"
- class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active"
+ disabled={openTask.loading}
+ class="rounded-none h-full w-[24px] p-0 border-none shadow-none data-[expanded]:bg-surface-raised-base-active disabled:!cursor-default"
+ classList={{
+ "bg-surface-raised-base-active": openTask.loading,
+ }}
aria-label={language.t("session.header.open.menu")}
/>
<DropdownMenu.Portal>
@@ -383,6 +407,7 @@ export function SessionHeader() {
{options().map((o) => (
<DropdownMenu.RadioItem
value={o.id}
+ disabled={openTask.loading}
onSelect={() => {
setMenu("open", false)
openDir(o.id)