mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-14 05:23:12 +00:00
fix(app): show Open in only in file and review headers (#62174)
This commit is contained in:
parent
32ab1f3a9e
commit
e23d54ca75
5 changed files with 41 additions and 52 deletions
|
|
@ -29,6 +29,7 @@ import {
|
|||
} from "@opencode/plugin/gui"
|
||||
import { current, useShared } from "./context"
|
||||
import SessionFileList, { applyFileListKeyDown } from "./list"
|
||||
import OpenInAppButton, { useOpenInApp } from "./open-in-app"
|
||||
import { fileTabPath, isFileTab } from "./path"
|
||||
import PierreFileTree from "./pierre-tree"
|
||||
import {
|
||||
|
|
@ -71,6 +72,19 @@ export function SessionFileBrowserTab(props: {
|
|||
const sidebarOpened = () => props.placeholder || props.state.opened()
|
||||
const query = createMemo(() => filter().trim())
|
||||
|
||||
const openIn = ctx.desktop
|
||||
? useOpenInApp({
|
||||
get session() {
|
||||
return props.session
|
||||
},
|
||||
path: () => file.root,
|
||||
})
|
||||
: undefined
|
||||
|
||||
// A file tab's header ends with "Open in": the tree's header while the tree is open, else the preview's.
|
||||
const openInShown = () => !props.mobile && !props.placeholder && !!file.root && !!openIn?.canOpen()
|
||||
const openInButton = () => <OpenInAppButton session={props.session} screen={props.screen} />
|
||||
|
||||
// The filter's search. It asks the server directly: the screen's search reads a failure as no results.
|
||||
const [searchState, setSearchState] = createSignal<FileSearchState>(INITIAL_FILE_SEARCH_STATE)
|
||||
const searches = new Map<number, { controller: AbortController; timer: ReturnType<typeof setTimeout> }>()
|
||||
|
|
@ -226,6 +240,7 @@ export function SessionFileBrowserTab(props: {
|
|||
<SessionFilePanelV2
|
||||
toolbar={!props.mobile && !props.placeholder && !sidebarOpened()}
|
||||
toolbarStart={<span class="truncate font-[530]">{title()}</span>}
|
||||
toolbarEnd={openInShown() ? openInButton() : undefined}
|
||||
sidebarCollapsed={!sidebarOpened()}
|
||||
sidebarToggleGap={10}
|
||||
sidebarToggle={
|
||||
|
|
@ -242,6 +257,7 @@ export function SessionFileBrowserTab(props: {
|
|||
open={sidebarOpened()}
|
||||
transition={props.state.transition()}
|
||||
title={<span class="truncate font-[530]">{title()}</span>}
|
||||
actions={openInShown() ? openInButton() : undefined}
|
||||
filter={filter()}
|
||||
onFilterChange={setFilter}
|
||||
onFilterKeyDown={onFilterKeyDown}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
Suspense,
|
||||
untrack,
|
||||
type ParentProps,
|
||||
type Signal,
|
||||
} from "solid-js"
|
||||
import { createStore, produce, unwrap } from "solid-js/store"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
|
|
@ -20,7 +19,6 @@ import {
|
|||
bindPlugin,
|
||||
LinkHandler,
|
||||
MenuItem,
|
||||
Slot,
|
||||
Style,
|
||||
usePanel,
|
||||
type Files,
|
||||
|
|
@ -534,21 +532,6 @@ const setup: Plugin.Setup<typeof File> = (ctx) => {
|
|||
import("./open-in-app").then((module) => ({ default: module.OpenInAppMenuItem })),
|
||||
)
|
||||
|
||||
// How many "Open in" buttons each screen's panel headers show; the tab strip shows its own only while none do.
|
||||
const headers = new WeakMap<SessionScreen, Signal<number>>()
|
||||
|
||||
const headerButtons = (screen: SessionScreen) => {
|
||||
const existing = headers.get(screen)
|
||||
|
||||
if (existing) return existing
|
||||
|
||||
const created = createSignal(0)
|
||||
|
||||
headers.set(screen, created)
|
||||
|
||||
return created
|
||||
}
|
||||
|
||||
if (desktop) {
|
||||
onCleanup(
|
||||
onIdle(() => {
|
||||
|
|
@ -566,37 +549,18 @@ const setup: Plugin.Setup<typeof File> = (ctx) => {
|
|||
</FileProvider>
|
||||
),
|
||||
})
|
||||
ctx.add(Slot, {
|
||||
at: "session.panel.end",
|
||||
render: (input) => (
|
||||
<Show when={headerButtons(input.screen)[0]() === 0}>
|
||||
<FileProvider>
|
||||
<Suspense>
|
||||
<OpenInAppButton session={input.session} screen={input.screen} />
|
||||
</Suspense>
|
||||
</FileProvider>
|
||||
</Show>
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
ctx.provide(OpenInApp, {
|
||||
Button: bindPlugin((props: OpenInAppProps) => {
|
||||
const count = headerButtons(props.screen)
|
||||
|
||||
count[1]((value) => value + 1)
|
||||
onCleanup(() => count[1]((value) => value - 1))
|
||||
|
||||
return (
|
||||
<Show when={desktop}>
|
||||
<FileProvider>
|
||||
<Suspense>
|
||||
<OpenInAppButton session={props.session} screen={props.screen} />
|
||||
</Suspense>
|
||||
</FileProvider>
|
||||
</Show>
|
||||
)
|
||||
}),
|
||||
Button: bindPlugin((props: OpenInAppProps) => (
|
||||
<Show when={desktop}>
|
||||
<FileProvider>
|
||||
<Suspense>
|
||||
<OpenInAppButton session={props.session} screen={props.screen} />
|
||||
</Suspense>
|
||||
</FileProvider>
|
||||
</Show>
|
||||
)),
|
||||
})
|
||||
|
||||
// The review panel lists its changed files with the browser's tree; it renders under this plugin, on the
|
||||
|
|
|
|||
|
|
@ -149,6 +149,12 @@ function ReviewPanel(props: ReviewPanelProps) {
|
|||
return live.status === "active" ? live.value : undefined
|
||||
}
|
||||
|
||||
const openInButton = () => (
|
||||
<Show when={openIn()}>
|
||||
{(openIn) => <Dynamic component={openIn().Button} screen={props.screen} session={props.session} />}
|
||||
</Show>
|
||||
)
|
||||
|
||||
const diffs = () => props.diffs
|
||||
|
||||
const searching = createMemo(() => props.state.filter().trim().length > 0)
|
||||
|
|
@ -313,11 +319,7 @@ function ReviewPanel(props: ReviewPanelProps) {
|
|||
sidebarToggle={
|
||||
<SessionReviewV2SidebarToggle opened={props.state.sidebar.opened()} onToggle={props.state.sidebar.toggle} />
|
||||
}
|
||||
toolbarAction={
|
||||
<Show when={openIn()}>
|
||||
{(openIn) => <Dynamic component={openIn().Button} screen={props.screen} session={props.session} />}
|
||||
</Show>
|
||||
}
|
||||
toolbarAction={openInButton()}
|
||||
sidebar={
|
||||
// Always mounted: the sidebar header hosts the changes-mode dropdown,
|
||||
// which must stay reachable when the current mode has zero diffs.
|
||||
|
|
@ -334,6 +336,8 @@ function ReviewPanel(props: ReviewPanelProps) {
|
|||
kinds={treeKinds()}
|
||||
folderLabels={props.folderLabels}
|
||||
activeDiff={activeDiff()}
|
||||
// Without diffs, the preview has no header while the tree is open.
|
||||
actions={diffs().length === 0 ? openInButton() : undefined}
|
||||
/>
|
||||
}
|
||||
activeFile={activeDiff()}
|
||||
|
|
@ -388,6 +392,7 @@ function ReviewPanelSidebar(props: {
|
|||
kinds: ReturnType<typeof reviewDiffKinds>
|
||||
folderLabels?: ReadonlyMap<string, string>
|
||||
activeDiff: string | undefined
|
||||
actions?: JSX.Element
|
||||
}) {
|
||||
const ctx = usePlugin<typeof Review>()
|
||||
|
||||
|
|
@ -426,6 +431,7 @@ function ReviewPanelSidebar(props: {
|
|||
transition={props.state.sidebar.transition()}
|
||||
title={props.title}
|
||||
stats={<DiffChanges changes={props.diffs} />}
|
||||
actions={props.actions}
|
||||
filter={props.state.filter()}
|
||||
onFilterChange={props.state.setFilter}
|
||||
onFilterKeyDown={onFilterKeyDown}
|
||||
|
|
|
|||
|
|
@ -77,10 +77,10 @@ export interface OpenInAppProps {
|
|||
readonly session: MountedSession
|
||||
}
|
||||
|
||||
/** The desktop "Open in" button, for a panel header that shows it in place of the tab strip's. */
|
||||
/** The desktop "Open in" button, for a panel header such as the review's. */
|
||||
export interface OpenInApp {
|
||||
/**
|
||||
* Renders nothing on the web or for a remote server. While it is mounted, the tab strip leaves out its own.
|
||||
* Renders nothing on the web or for a remote server.
|
||||
*
|
||||
* @param props - The workspace to open.
|
||||
* @returns The button.
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ export type SessionReviewV2SidebarProps = {
|
|||
transition: boolean
|
||||
title?: JSX.Element
|
||||
stats?: JSX.Element
|
||||
/** Controls at the header's end, after the stats. */
|
||||
actions?: JSX.Element
|
||||
filter: string
|
||||
onFilterChange: (value: string) => void
|
||||
onFilterKeyDown?: JSX.EventHandlerUnion<HTMLInputElement, KeyboardEvent>
|
||||
|
|
@ -80,6 +82,7 @@ export function SessionReviewV2Sidebar(props: SessionReviewV2SidebarProps) {
|
|||
<div data-slot="session-review-v2-sidebar-header">
|
||||
<div data-slot="session-review-v2-sidebar-title">{props.title}</div>
|
||||
{props.stats}
|
||||
{props.actions}
|
||||
</div>
|
||||
<div data-slot="session-review-v2-sidebar-filter">
|
||||
<TextInput
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue