fix(desktop): context menu button / tab intermittent issue (#34420)
Co-authored-by: Brendan Allan <git@brendonovich.dev>
This commit is contained in:
parent
1982d9832b
commit
003c22b4a3
4 changed files with 71 additions and 24 deletions
|
|
@ -26,7 +26,7 @@ function openSessionContext(args: {
|
||||||
layout: ReturnType<typeof useLayout>
|
layout: ReturnType<typeof useLayout>
|
||||||
tabs: ReturnType<ReturnType<typeof useLayout>["tabs"]>
|
tabs: ReturnType<ReturnType<typeof useLayout>["tabs"]>
|
||||||
}) {
|
}) {
|
||||||
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
|
args.view.reviewPanel.open(args.view.reviewPanel.opened() ? "other" : "context-button")
|
||||||
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
|
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
|
||||||
void args.tabs.open("context")
|
void args.tabs.open("context")
|
||||||
args.tabs.setActive("context")
|
args.tabs.setActive("context")
|
||||||
|
|
@ -64,16 +64,21 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||||
const cost = createMemo(() => {
|
const cost = createMemo(() => {
|
||||||
return usd().format(info()?.cost ?? 0)
|
return usd().format(info()?.cost ?? 0)
|
||||||
})
|
})
|
||||||
|
const contextVisible = createMemo(() => view().reviewPanel.opened() && tabState.activeTab() === "context")
|
||||||
|
const hasOtherTabs = createMemo(() => tabs().all().some((tab) => tab !== "context" && tab !== "review"))
|
||||||
|
|
||||||
const openContext = () => {
|
const openContext = () => {
|
||||||
if (!params.id) return
|
if (!params.id) return
|
||||||
|
|
||||||
if (tabState.activeTab() === "context") {
|
const sessionView = view()
|
||||||
|
if (contextVisible()) {
|
||||||
tabs().close("context")
|
tabs().close("context")
|
||||||
|
if (sessionView.reviewPanel.source() === "context-button" && !hasOtherTabs()) sessionView.reviewPanel.close()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
openSessionContext({
|
openSessionContext({
|
||||||
view: view(),
|
view: sessionView,
|
||||||
layout,
|
layout,
|
||||||
tabs: tabs(),
|
tabs: tabs(),
|
||||||
})
|
})
|
||||||
|
|
@ -81,7 +86,20 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||||
|
|
||||||
const circle = () => (
|
const circle = () => (
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<ProgressCircle size={16} strokeWidth={2} percentage={context()?.usage ?? 0} />
|
<ProgressCircle
|
||||||
|
size={16}
|
||||||
|
strokeWidth={2}
|
||||||
|
percentage={context()?.usage ?? 0}
|
||||||
|
style={
|
||||||
|
variant() === "indicator"
|
||||||
|
? {
|
||||||
|
"--progress-circle-background": "var(--v2-background-bg-layer-04, var(--border-weak-base))",
|
||||||
|
"--progress-circle-background-overlay": "var(--v2-overlay-simple-overlay-pressed, transparent)",
|
||||||
|
"--progress-circle-progress": "var(--v2-icon-icon-base, var(--icon-base))",
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
const circleV2 = () => (
|
const circleV2 = () => (
|
||||||
|
|
@ -119,10 +137,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Show when={params.id}>
|
<Show when={params.id}>
|
||||||
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
|
<Switch>
|
||||||
<Switch>
|
<Match when={variant() === "indicator"}>{circle()}</Match>
|
||||||
<Match when={variant() === "indicator"}>{circle()}</Match>
|
<Match when={buttonAppearance() === "v2"}>
|
||||||
<Match when={buttonAppearance() === "v2"}>
|
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
|
||||||
<IconButtonV2
|
<IconButtonV2
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost-muted"
|
variant="ghost-muted"
|
||||||
|
|
@ -131,8 +149,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||||
onClick={openContext}
|
onClick={openContext}
|
||||||
aria-label={language.t("context.usage.view")}
|
aria-label={language.t("context.usage.view")}
|
||||||
/>
|
/>
|
||||||
</Match>
|
</Tooltip>
|
||||||
<Match when={true}>
|
</Match>
|
||||||
|
<Match when={true}>
|
||||||
|
<Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|
@ -142,9 +162,9 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
|
||||||
>
|
>
|
||||||
{circle()}
|
{circle()}
|
||||||
</Button>
|
</Button>
|
||||||
</Match>
|
</Tooltip>
|
||||||
</Switch>
|
</Match>
|
||||||
</Tooltip>
|
</Switch>
|
||||||
</Show>
|
</Show>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ export type LocalProject = Partial<Project> & { worktree: string; expanded: bool
|
||||||
export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
|
export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
|
||||||
|
|
||||||
export type ReviewDiffStyle = "unified" | "split"
|
export type ReviewDiffStyle = "unified" | "split"
|
||||||
|
export type ReviewPanelSource = "context-button" | "other"
|
||||||
|
|
||||||
export type LayoutRoute =
|
export type LayoutRoute =
|
||||||
| { type: "home" }
|
| { type: "home" }
|
||||||
|
|
@ -304,6 +305,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
const [ephemeral, setEphemeral] = createStore({
|
||||||
|
reviewPanelSource: "other" as ReviewPanelSource,
|
||||||
|
})
|
||||||
|
|
||||||
const MAX_SESSION_KEYS = 50
|
const MAX_SESSION_KEYS = 50
|
||||||
const PENDING_MESSAGE_TTL_MS = 2 * 60 * 1000
|
const PENDING_MESSAGE_TTL_MS = 2 * 60 * 1000
|
||||||
|
|
@ -780,6 +784,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||||
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
|
const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
|
||||||
const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
|
const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
|
||||||
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED)
|
const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED)
|
||||||
|
const reviewPanelSource = createMemo(() => (reviewPanelOpened() ? ephemeral.reviewPanelSource : "other"))
|
||||||
|
|
||||||
function setTerminalOpened(next: boolean) {
|
function setTerminalOpened(next: boolean) {
|
||||||
const current = store.terminal
|
const current = store.terminal
|
||||||
|
|
@ -793,16 +798,26 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||||
setStore("terminal", "opened", next)
|
setStore("terminal", "opened", next)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setReviewPanelOpened(next: boolean) {
|
function setReviewPanelOpened(next: boolean, source: ReviewPanelSource) {
|
||||||
|
const nextSource = next ? source : "other"
|
||||||
const current = store.review
|
const current = store.review
|
||||||
if (!current) {
|
if (!current) {
|
||||||
setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
|
batch(() => {
|
||||||
|
setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
|
||||||
|
setEphemeral("reviewPanelSource", nextSource)
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const value = current.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED
|
const value = current.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED
|
||||||
if (value === next) return
|
if (value === next) {
|
||||||
setStore("review", "panelOpened", next)
|
if (ephemeral.reviewPanelSource !== nextSource) setEphemeral("reviewPanelSource", nextSource)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
batch(() => {
|
||||||
|
setStore("review", "panelOpened", next)
|
||||||
|
setEphemeral("reviewPanelSource", nextSource)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -838,14 +853,15 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
|
||||||
},
|
},
|
||||||
reviewPanel: {
|
reviewPanel: {
|
||||||
opened: reviewPanelOpened,
|
opened: reviewPanelOpened,
|
||||||
open() {
|
source: reviewPanelSource,
|
||||||
setReviewPanelOpened(true)
|
open(source: ReviewPanelSource = "other") {
|
||||||
|
setReviewPanelOpened(true, source)
|
||||||
},
|
},
|
||||||
close() {
|
close() {
|
||||||
setReviewPanelOpened(false)
|
setReviewPanelOpened(false, "other")
|
||||||
},
|
},
|
||||||
toggle() {
|
toggle() {
|
||||||
setReviewPanelOpened(!reviewPanelOpened())
|
setReviewPanelOpened(!reviewPanelOpened(), "other")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
review: {
|
review: {
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,15 @@
|
||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
|
|
||||||
[data-slot="progress-circle-background"] {
|
[data-slot="progress-circle-background"] {
|
||||||
stroke: var(--border-weak-base);
|
stroke: var(--progress-circle-background, var(--border-weak-base));
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-slot="progress-circle-background-overlay"] {
|
||||||
|
stroke: var(--progress-circle-background-overlay, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-slot="progress-circle-progress"] {
|
[data-slot="progress-circle-progress"] {
|
||||||
stroke: var(--border-active);
|
stroke: var(--progress-circle-progress, var(--border-active));
|
||||||
transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
|
transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { type ComponentProps, createMemo, splitProps } from "solid-js"
|
import { type ComponentProps, createMemo, splitProps } from "solid-js"
|
||||||
|
|
||||||
export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList"> {
|
export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList" | "style"> {
|
||||||
percentage: number
|
percentage: number
|
||||||
size?: number
|
size?: number
|
||||||
strokeWidth?: number
|
strokeWidth?: number
|
||||||
|
|
@ -43,6 +43,13 @@ export function ProgressCircle(props: ProgressCircleProps) {
|
||||||
data-slot="progress-circle-background"
|
data-slot="progress-circle-background"
|
||||||
stroke-width={strokeWidth()}
|
stroke-width={strokeWidth()}
|
||||||
/>
|
/>
|
||||||
|
<circle
|
||||||
|
cx={center}
|
||||||
|
cy={center}
|
||||||
|
r={radius()}
|
||||||
|
data-slot="progress-circle-background-overlay"
|
||||||
|
stroke-width={strokeWidth()}
|
||||||
|
/>
|
||||||
<circle
|
<circle
|
||||||
cx={center}
|
cx={center}
|
||||||
cy={center}
|
cy={center}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue