feat(desktop): scope tabs to windows (#34669)

This commit is contained in:
Brendan Allan 2026-07-01 15:02:10 +08:00 committed by GitHub
commit af72cec799
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 185 additions and 42 deletions

View file

@ -63,7 +63,10 @@ const [updaterState, setUpdaterState] = createSignal<UpdaterState>({ status: "di
void window.api.updater.subscribe(setUpdaterState)
const deepLinkEvent = "opencode:deep-link"
const lastActiveUrlKey = "opencode.desktop.last-active-url"
type DesktopWindowState = {
id?: string
}
const emitDeepLinks = (urls: string[]) => {
if (urls.length === 0) return
@ -78,31 +81,35 @@ const listenForDeepLinks = () => {
return window.api.onDeepLink((urls) => emitDeepLinks(urls))
}
function getLastActiveUrl() {
function windowLastActiveUrlKey(windowID: string) {
return `opencode.desktop.window.${windowID}.last-active-url`
}
function getLastActiveUrl(windowID: string) {
if (typeof localStorage !== "object") return "/"
try {
const value = localStorage.getItem(lastActiveUrlKey)
const value = localStorage.getItem(windowLastActiveUrlKey(windowID))
if (value?.startsWith("/") && !value.startsWith("//")) return value
} catch {}
return "/"
}
function setLastActiveUrl(value: string) {
function setLastActiveUrl(windowID: string, value: string) {
if (typeof localStorage !== "object") return
try {
localStorage.setItem(lastActiveUrlKey, value)
localStorage.setItem(windowLastActiveUrlKey(windowID), value)
} catch {}
}
function DesktopMemoryRouter(props: BaseRouterProps) {
function DesktopMemoryRouter(props: BaseRouterProps & { windowID: string }) {
const history = createMemoryHistory()
const initialUrl = getLastActiveUrl()
const initialUrl = getLastActiveUrl(props.windowID)
if (initialUrl !== "/") history.set({ value: initialUrl, replace: true, scroll: false })
onCleanup(history.listen(setLastActiveUrl))
onCleanup(history.listen((value) => setLastActiveUrl(props.windowID, value)))
return <MemoryRouter {...props} history={history} />
}
const createPlatform = (): Platform => {
const createPlatform = (windowState: DesktopWindowState): Platform => {
const attachmentPaths = new WeakMap<File, string>()
const os = (() => {
const ua = navigator.userAgent
@ -161,6 +168,7 @@ const createPlatform = (): Platform => {
platform: "desktop",
os,
version: pkg.version,
windowID: windowState.id,
async openDirectoryPickerDialog(opts) {
return window.api.openDirectoryPicker({
@ -307,8 +315,16 @@ window.api.onMenuCommand((id) => {
})
listenForDeepLinks()
render(() => {
const platform = createPlatform()
function LoadingSplash() {
return (
<div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base">
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
</div>
)
}
function DesktopRoot(props: { windowState: DesktopWindowState }) {
const platform = createPlatform(props.windowState)
const loadLocale = async () => {
const current = await platform.storage?.("opencode.global.dat").getItem("language")
const legacy = current ? undefined : await platform.storage?.().getItem("language.v1")
@ -328,6 +344,7 @@ render(() => {
const [defaultServer] = createResource(() => platform.getDefaultServer?.())
const [locale] = createResource(loadLocale)
const router = (props: BaseRouterProps) => <DesktopMemoryRouter {...props} windowID={platform.windowID ?? "browser"} />
function handleClick(e: MouseEvent) {
const link = (e.target as HTMLElement).closest("a.external-link") as HTMLAnchorElement | null
@ -357,12 +374,6 @@ render(() => {
function App() {
const wslServers = useWslServers()
const splash = (
<div class="h-dvh w-screen flex flex-col items-center justify-center bg-background-base">
<Splash class="w-16 h-20 opacity-50 animate-pulse" />
</div>
)
const ready = createMemo(
() => !defaultServer.loading && !sidecar.loading && !windowCount.loading && !locale.loading,
)
@ -389,10 +400,10 @@ render(() => {
)
return (
<Show when={ready()} fallback={splash}>
<Show when={ready()} fallback={<LoadingSplash />}>
<Show when={effectiveDefaultServer()} keyed>
{(key) => (
<AppInterface defaultServer={key} servers={servers()} router={DesktopMemoryRouter}>
<AppInterface defaultServer={key} servers={servers()} router={router}>
<Inner />
</AppInterface>
)}
@ -415,4 +426,19 @@ render(() => {
</AppBaseProviders>
</PlatformProvider>
)
}
render(() => {
const [windowState] = createResource(async () => {
const api = window.api as typeof window.api & {
getWindowID?: () => Promise<string>
}
return { id: await api.getWindowID?.() }
})
return (
<Show when={windowState.latest} fallback={<LoadingSplash />} keyed>
{(state) => <DesktopRoot windowState={state} />}
</Show>
)
}, root!)