fix(app): close review pane
This commit is contained in:
@@ -44,7 +44,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
"session.previous",
|
"session.previous",
|
||||||
"session.next",
|
"session.next",
|
||||||
"terminal.toggle",
|
"terminal.toggle",
|
||||||
"fileTree.toggle",
|
"review.toggle",
|
||||||
]
|
]
|
||||||
const limit = 5
|
const limit = 5
|
||||||
|
|
||||||
@@ -162,6 +162,7 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
|
|||||||
const value = file.tab(path)
|
const value = file.tab(path)
|
||||||
tabs().open(value)
|
tabs().open(value)
|
||||||
file.load(path)
|
file.load(path)
|
||||||
|
layout.fileTree.open()
|
||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
props.onOpenFile?.(path)
|
props.onOpenFile?.(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,12 +189,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
|||||||
|
|
||||||
const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path))
|
const wantsReview = item.commentOrigin === "review" || (item.commentOrigin !== "file" && commentInReview(item.path))
|
||||||
if (wantsReview) {
|
if (wantsReview) {
|
||||||
|
layout.fileTree.open()
|
||||||
layout.fileTree.setTab("changes")
|
layout.fileTree.setTab("changes")
|
||||||
if (!layout.fileTree.opened()) tabs().open("review")
|
|
||||||
requestAnimationFrame(() => comments.setFocus(focus))
|
requestAnimationFrame(() => comments.setFocus(focus))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
layout.fileTree.open()
|
||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
const tab = files.tab(item.path)
|
const tab = files.tab(item.path)
|
||||||
tabs().open(tab)
|
tabs().open(tab)
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
|||||||
|
|
||||||
const openContext = () => {
|
const openContext = () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
|
layout.fileTree.open()
|
||||||
layout.fileTree.setTab("all")
|
layout.fileTree.setTab("all")
|
||||||
tabs().open("context")
|
tabs().open("context")
|
||||||
tabs().setActive("context")
|
tabs().setActive("context")
|
||||||
|
|||||||
@@ -280,17 +280,14 @@ export function SessionHeader() {
|
|||||||
</TooltipKeybind>
|
</TooltipKeybind>
|
||||||
</div>
|
</div>
|
||||||
<div class="hidden md:block shrink-0">
|
<div class="hidden md:block shrink-0">
|
||||||
<TooltipKeybind
|
<TooltipKeybind title={language.t("command.review.toggle")} keybind={command.keybind("review.toggle")}>
|
||||||
title={language.t("command.fileTree.toggle")}
|
|
||||||
keybind={command.keybind("fileTree.toggle")}
|
|
||||||
>
|
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class="group/file-tree-toggle size-6 p-0"
|
class="group/file-tree-toggle size-6 p-0"
|
||||||
onClick={() => layout.fileTree.toggle()}
|
onClick={() => layout.fileTree.toggle()}
|
||||||
aria-label={language.t("command.fileTree.toggle")}
|
aria-label={language.t("command.review.toggle")}
|
||||||
aria-expanded={layout.fileTree.opened()}
|
aria-expanded={layout.fileTree.opened()}
|
||||||
aria-controls="file-tree-panel"
|
aria-controls="review-panel"
|
||||||
>
|
>
|
||||||
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
<div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
|
||||||
<Icon
|
<Icon
|
||||||
|
|||||||
@@ -624,10 +624,11 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
|
const tabs = createMemo(() => store.sessionTabs[key()] ?? { all: [] })
|
||||||
return {
|
return {
|
||||||
tabs,
|
tabs,
|
||||||
active: createMemo(() => tabs().active),
|
active: createMemo(() => (tabs().active === "review" ? undefined : tabs().active)),
|
||||||
all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
|
all: createMemo(() => tabs().all.filter((tab) => tab !== "review")),
|
||||||
setActive(tab: string | undefined) {
|
setActive(tab: string | undefined) {
|
||||||
const session = key()
|
const session = key()
|
||||||
|
if (tab === "review") return
|
||||||
if (!store.sessionTabs[session]) {
|
if (!store.sessionTabs[session]) {
|
||||||
setStore("sessionTabs", session, { all: [], active: tab })
|
setStore("sessionTabs", session, { all: [], active: tab })
|
||||||
} else {
|
} else {
|
||||||
@@ -644,18 +645,10 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
async open(tab: string) {
|
async open(tab: string) {
|
||||||
|
if (tab === "review") return
|
||||||
const session = key()
|
const session = key()
|
||||||
const current = store.sessionTabs[session] ?? { all: [] }
|
const current = store.sessionTabs[session] ?? { all: [] }
|
||||||
|
|
||||||
if (tab === "review") {
|
|
||||||
if (!store.sessionTabs[session]) {
|
|
||||||
setStore("sessionTabs", session, { all: current.all, active: tab })
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setStore("sessionTabs", session, "active", tab)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tab === "context") {
|
if (tab === "context") {
|
||||||
const all = [tab, ...current.all.filter((x) => x !== tab)]
|
const all = [tab, ...current.all.filter((x) => x !== tab)]
|
||||||
if (!store.sessionTabs[session]) {
|
if (!store.sessionTabs[session]) {
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button"
|
|||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { Icon } from "@opencode-ai/ui/icon"
|
import { Icon } from "@opencode-ai/ui/icon"
|
||||||
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
|
||||||
import { DiffChanges } from "@opencode-ai/ui/diff-changes"
|
|
||||||
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
import { ResizeHandle } from "@opencode-ai/ui/resize-handle"
|
||||||
import { Tabs } from "@opencode-ai/ui/tabs"
|
import { Tabs } from "@opencode-ai/ui/tabs"
|
||||||
import { useCodeComponent } from "@opencode-ai/ui/context/code"
|
import { useCodeComponent } from "@opencode-ai/ui/context/code"
|
||||||
@@ -325,6 +324,7 @@ export default function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isDesktop = createMediaQuery("(min-width: 768px)")
|
const isDesktop = createMediaQuery("(min-width: 768px)")
|
||||||
|
const centered = createMemo(() => isDesktop() && !layout.fileTree.opened())
|
||||||
|
|
||||||
function normalizeTab(tab: string) {
|
function normalizeTab(tab: string) {
|
||||||
if (!tab.startsWith("file://")) return tab
|
if (!tab.startsWith("file://")) return tab
|
||||||
@@ -730,8 +730,8 @@ export default function Page() {
|
|||||||
onSelect: () => view().terminal.toggle(),
|
onSelect: () => view().terminal.toggle(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "fileTree.toggle",
|
id: "review.toggle",
|
||||||
title: language.t("command.fileTree.toggle"),
|
title: language.t("command.review.toggle"),
|
||||||
description: "",
|
description: "",
|
||||||
category: language.t("command.category.view"),
|
category: language.t("command.category.view"),
|
||||||
keybind: "mod+shift+r",
|
keybind: "mod+shift+r",
|
||||||
@@ -1097,12 +1097,11 @@ export default function Page() {
|
|||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
const reviewTab = createMemo(() => isDesktop() && !layout.fileTree.opened())
|
|
||||||
const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context"))
|
const contextOpen = createMemo(() => tabs().active() === "context" || tabs().all().includes("context"))
|
||||||
const openedTabs = createMemo(() =>
|
const openedTabs = createMemo(() =>
|
||||||
tabs()
|
tabs()
|
||||||
.all()
|
.all()
|
||||||
.filter((tab) => tab !== "context" && tab !== "review"),
|
.filter((tab) => tab !== "context"),
|
||||||
)
|
)
|
||||||
|
|
||||||
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
|
const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes")
|
||||||
@@ -1271,56 +1270,29 @@ export default function Page() {
|
|||||||
const activeTab = createMemo(() => {
|
const activeTab = createMemo(() => {
|
||||||
const active = tabs().active()
|
const active = tabs().active()
|
||||||
if (active === "context") return "context"
|
if (active === "context") return "context"
|
||||||
if (active === "review" && reviewTab()) return "review"
|
|
||||||
if (active && file.pathFromTab(active)) return normalizeTab(active)
|
if (active && file.pathFromTab(active)) return normalizeTab(active)
|
||||||
|
|
||||||
const first = openedTabs()[0]
|
const first = openedTabs()[0]
|
||||||
if (first) return first
|
if (first) return first
|
||||||
if (contextOpen()) return "context"
|
if (contextOpen()) return "context"
|
||||||
if (reviewTab() && hasReview()) return "review"
|
|
||||||
return "empty"
|
return "empty"
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (!layout.ready()) return
|
if (!layout.ready()) return
|
||||||
if (tabs().active()) return
|
if (tabs().active()) return
|
||||||
if (openedTabs().length === 0 && !contextOpen() && !(reviewTab() && hasReview())) return
|
if (openedTabs().length === 0 && !contextOpen()) return
|
||||||
|
|
||||||
const next = activeTab()
|
const next = activeTab()
|
||||||
if (next === "empty") return
|
if (next === "empty") return
|
||||||
tabs().setActive(next)
|
tabs().setActive(next)
|
||||||
})
|
})
|
||||||
|
|
||||||
createEffect(
|
|
||||||
on(
|
|
||||||
() => layout.fileTree.opened(),
|
|
||||||
(opened, prev) => {
|
|
||||||
if (prev === undefined) return
|
|
||||||
if (!isDesktop()) return
|
|
||||||
|
|
||||||
if (opened) {
|
|
||||||
const active = tabs().active()
|
|
||||||
const tab = active === "review" || (!active && hasReview()) ? "changes" : "all"
|
|
||||||
layout.fileTree.setTab(tab)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fileTreeTab() !== "changes") return
|
|
||||||
tabs().setActive("review")
|
|
||||||
},
|
|
||||||
{ defer: true },
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const id = params.id
|
const id = params.id
|
||||||
if (!id) return
|
if (!id) return
|
||||||
|
|
||||||
const wants = isDesktop()
|
const wants = isDesktop() ? layout.fileTree.opened() && fileTreeTab() === "changes" : store.mobileTab === "changes"
|
||||||
? layout.fileTree.opened()
|
|
||||||
? fileTreeTab() === "changes"
|
|
||||||
: activeTab() === "review"
|
|
||||||
: store.mobileTab === "changes"
|
|
||||||
if (!wants) return
|
if (!wants) return
|
||||||
if (sync.data.session_diff[id] !== undefined) return
|
if (sync.data.session_diff[id] !== undefined) return
|
||||||
if (sync.status === "loading") return
|
if (sync.status === "loading") return
|
||||||
@@ -1789,10 +1761,11 @@ export default function Page() {
|
|||||||
<div
|
<div
|
||||||
classList={{
|
classList={{
|
||||||
"@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger": true,
|
"@container relative shrink-0 flex flex-col min-h-0 h-full bg-background-stronger": true,
|
||||||
"flex-1 md:flex-none pt-6 md:pt-3": true,
|
"flex-1 pt-6 md:pt-3": true,
|
||||||
|
"md:flex-none": layout.fileTree.opened(),
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: isDesktop() ? `${layout.session.width()}px` : "100%",
|
width: isDesktop() && layout.fileTree.opened() ? `${layout.session.width()}px` : "100%",
|
||||||
"--prompt-height": store.promptHeight ? `${store.promptHeight}px` : undefined,
|
"--prompt-height": store.promptHeight ? `${store.promptHeight}px` : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -1967,6 +1940,7 @@ export default function Page() {
|
|||||||
"sticky top-0 z-30 bg-background-stronger": true,
|
"sticky top-0 z-30 bg-background-stronger": true,
|
||||||
"w-full": true,
|
"w-full": true,
|
||||||
"px-4 md:px-6": true,
|
"px-4 md:px-6": true,
|
||||||
|
"md:max-w-200 md:mx-auto": centered(),
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div class="h-10 flex items-center gap-1">
|
<div class="h-10 flex items-center gap-1">
|
||||||
@@ -1991,7 +1965,13 @@ export default function Page() {
|
|||||||
<div
|
<div
|
||||||
ref={autoScroll.contentRef}
|
ref={autoScroll.contentRef}
|
||||||
role="log"
|
role="log"
|
||||||
class="flex flex-col gap-32 items-start justify-start w-full mt-0 pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
|
class="flex flex-col gap-32 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
|
||||||
|
classList={{
|
||||||
|
"w-full": true,
|
||||||
|
"md:max-w-200 md:mx-auto": centered(),
|
||||||
|
"mt-0.5": centered(),
|
||||||
|
"mt-0": !centered(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Show when={store.turnStart > 0}>
|
<Show when={store.turnStart > 0}>
|
||||||
<div class="w-full flex justify-center">
|
<div class="w-full flex justify-center">
|
||||||
@@ -2039,7 +2019,10 @@ export default function Page() {
|
|||||||
<div
|
<div
|
||||||
id={anchor(message.id)}
|
id={anchor(message.id)}
|
||||||
data-message-id={message.id}
|
data-message-id={message.id}
|
||||||
class="min-w-0 w-full max-w-full"
|
classList={{
|
||||||
|
"min-w-0 w-full max-w-full": true,
|
||||||
|
"md:max-w-200": centered(),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<SessionTurn
|
<SessionTurn
|
||||||
sessionID={params.id!}
|
sessionID={params.id!}
|
||||||
@@ -2092,7 +2075,12 @@ export default function Page() {
|
|||||||
ref={(el) => (promptDock = el)}
|
ref={(el) => (promptDock = el)}
|
||||||
class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 px-4 md:px-0 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none"
|
class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 px-4 md:px-0 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none"
|
||||||
>
|
>
|
||||||
<div class="w-full px-4 pointer-events-auto">
|
<div
|
||||||
|
classList={{
|
||||||
|
"w-full px-4 pointer-events-auto": true,
|
||||||
|
"md:max-w-200 md:mx-auto": centered(),
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Show when={request()} keyed>
|
<Show when={request()} keyed>
|
||||||
{(perm) => (
|
{(perm) => (
|
||||||
<div data-component="tool-part-wrapper" data-permission="true" class="mb-3">
|
<div data-component="tool-part-wrapper" data-permission="true" class="mb-3">
|
||||||
@@ -2163,7 +2151,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={isDesktop()}>
|
<Show when={isDesktop() && layout.fileTree.opened()}>
|
||||||
<ResizeHandle
|
<ResizeHandle
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
size={layout.session.width()}
|
size={layout.session.width()}
|
||||||
@@ -2175,7 +2163,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Desktop side panel - hidden on mobile */}
|
{/* Desktop side panel - hidden on mobile */}
|
||||||
<Show when={isDesktop()}>
|
<Show when={isDesktop() && layout.fileTree.opened()}>
|
||||||
<aside
|
<aside
|
||||||
id="review-panel"
|
id="review-panel"
|
||||||
aria-label={language.t("session.panel.reviewAndFiles")}
|
aria-label={language.t("session.panel.reviewAndFiles")}
|
||||||
@@ -2183,7 +2171,7 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
<div class="flex-1 min-w-0 h-full">
|
<div class="flex-1 min-w-0 h-full">
|
||||||
<Show
|
<Show
|
||||||
when={layout.fileTree.opened() && fileTreeTab() === "changes"}
|
when={fileTreeTab() === "changes"}
|
||||||
fallback={
|
fallback={
|
||||||
<DragDropProvider
|
<DragDropProvider
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
@@ -2196,23 +2184,6 @@ export default function Page() {
|
|||||||
<Tabs value={activeTab()} onChange={openTab}>
|
<Tabs value={activeTab()} onChange={openTab}>
|
||||||
<div class="sticky top-0 shrink-0 flex">
|
<div class="sticky top-0 shrink-0 flex">
|
||||||
<Tabs.List>
|
<Tabs.List>
|
||||||
<Show when={reviewTab()}>
|
|
||||||
<Tabs.Trigger value="review" classes={{ button: "!pl-6" }}>
|
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<Show when={hasReview() && diffsReady()}>
|
|
||||||
<DiffChanges changes={diffs()} variant="bars" />
|
|
||||||
</Show>
|
|
||||||
<div class="flex items-center gap-1.5">
|
|
||||||
<div>{language.t("session.tab.review")}</div>
|
|
||||||
<Show when={hasReview()}>
|
|
||||||
<div class="text-12-medium text-text-strong h-4 px-2 flex flex-col items-center justify-center rounded-full bg-surface-base">
|
|
||||||
{reviewCount()}
|
|
||||||
</div>
|
|
||||||
</Show>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Tabs.Trigger>
|
|
||||||
</Show>
|
|
||||||
<Show when={contextOpen()}>
|
<Show when={contextOpen()}>
|
||||||
<Tabs.Trigger
|
<Tabs.Trigger
|
||||||
value="context"
|
value="context"
|
||||||
@@ -2261,12 +2232,6 @@ export default function Page() {
|
|||||||
</Tabs.List>
|
</Tabs.List>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Show when={reviewTab()}>
|
|
||||||
<Tabs.Content value="review" class="flex flex-col h-full overflow-hidden contain-strict">
|
|
||||||
<Show when={activeTab() === "review"}>{reviewPanel()}</Show>
|
|
||||||
</Tabs.Content>
|
|
||||||
</Show>
|
|
||||||
|
|
||||||
<Tabs.Content value="empty" class="flex flex-col h-full overflow-hidden contain-strict">
|
<Tabs.Content value="empty" class="flex flex-col h-full overflow-hidden contain-strict">
|
||||||
<Show when={activeTab() === "empty"}>
|
<Show when={activeTab() === "empty"}>
|
||||||
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
<div class="relative pt-2 flex-1 min-h-0 overflow-hidden">
|
||||||
|
|||||||
Reference in New Issue
Block a user