feat(vcs): resize the Push dialog's commits and changed files with a splitter

The commits list and the changed files tree had fixed heights, so a push with
one commit and many files left the files in a small scroll area and empty space
above the buttons. They now sit in a vertical split that fills the dialog body,
and the divider position is remembered.
This commit is contained in:
l0ng-ai
2026-11-06 00:37:33 +08:00
parent ad64506336
commit 7554507a6e
2 changed files with 101 additions and 37 deletions
+19 -2
View File
@@ -129,15 +129,32 @@
flex-direction: column;
padding: 0 14px 14px;
}
/* Push dialog: commits above their changed files, split by a draggable divider; the pair fills the body. */
.pushSplit {
flex: 1;
min-height: min(540px, 60vh);
}
/* The panel's own `height: 100%` doesn't resolve against a flex-basis height in WebKit: stretch instead. */
.pushSplit .pushSplitPanel {
height: auto;
align-self: stretch;
}
.pushSplitPanel > .pushListWrap {
min-height: 0;
padding-bottom: 8px;
}
/* Push dialogs: the outgoing commits' changed files below the commits, the diff beside them. */
.pushChanges {
flex: none;
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
padding-top: 8px;
}
.pushChanges .comparison {
height: min(360px, 45vh);
flex: 1;
height: auto;
min-height: 0;
padding: 0 14px 14px;
}
.pushChangesTitle {
+82 -35
View File
@@ -5,7 +5,7 @@ import { call, commands } from "@/platform/ipc";
import { tx } from "@/platform/i18n";
import { workflowSettings } from "@/platform/workflowSettings";
import { notifications } from "@/workbench/contributions";
import { Checkbox, Dialog, Input, List, Select, Spinner, T, Tree } from "@/ui";
import { Checkbox, Dialog, Input, List, Select, Spinner, SplitGroup, SplitHandle, SplitPanel, T, Tree } from "@/ui";
import { ask } from "@/workbench/messages";
import { openDialog } from "./dialogs";
import { formatTime } from "./model";
@@ -340,6 +340,11 @@ function PushDialog({ target: pushTarget, onClose }: { target: PushTarget; onClo
const [commits, setCommits] = useState<Commit[] | null>(null);
// The commit whose changes are shown; -1: all outgoing commits.
const [shownCommit, setShownCommit] = useState(-1);
const [pushSplit, setPushSplit] = useState(loadPushSplit);
const keepPushSplit = (layout: Record<string, number>) => {
setPushSplit(layout);
savePushSplit(layout);
};
const shownCommits = useMemo(
() => (commits && shownCommit >= 0 && commits[shownCommit] ? [commits[shownCommit]] : (commits ?? [])),
[commits, shownCommit],
@@ -440,6 +445,36 @@ function PushDialog({ target: pushTarget, onClose }: { target: PushTarget; onClo
);
};
const commitList = (
<div className={styles.pushListWrap}>
{error ? (
<div className={styles.error}>{error}</div>
) : !commits ? (
<div className={styles.empty}>
<Spinner size={14} />
</div>
) : (
<List
items={commits}
getKey={(c) => c.id}
activeIndex={shownCommit}
onActiveChange={setShownCommit}
rowHeight={22}
aria-label="Outgoing commits"
className={styles.boxList}
empty={<span>{upstream ? tx("Everything is up to date") : tx("No commits")}</span>}
renderItem={(c) => (
<span className={styles.commitRow} style={{ flex: 1, padding: 0 }}>
<span className={styles.commitHash}>{c.shortId}</span>
<span className={styles.commitSubject}>{c.summary}</span>
<span className={styles.commitMeta}>{c.author}</span>
<span className={styles.commitMeta}>{formatTime(c.timeMs)}</span>
</span>
)}
/>
)}
</div>
);
return (
<Dialog
open
@@ -520,44 +555,56 @@ function PushDialog({ target: pushTarget, onClose }: { target: PushTarget; onClo
)}
{retarget && <span className={styles.hint}> {tx("New")}</span>}
</div>
<div className={styles.pushListWrap}>
{error ? (
<div className={styles.error}>{error}</div>
) : !commits ? (
<div className={styles.empty}>
<Spinner size={14} />
</div>
) : (
<List
items={commits}
getKey={(c) => c.id}
activeIndex={shownCommit}
onActiveChange={setShownCommit}
rowHeight={22}
aria-label="Outgoing commits"
className={styles.boxList}
empty={<span>{upstream ? tx("Everything is up to date") : tx("No commits")}</span>}
renderItem={(c) => (
<span className={styles.commitRow} style={{ flex: 1, padding: 0 }}>
<span className={styles.commitHash}>{c.shortId}</span>
<span className={styles.commitSubject}>{c.summary}</span>
<span className={styles.commitMeta}>{c.author}</span>
<span className={styles.commitMeta}>{formatTime(c.timeMs)}</span>
</span>
)}
/>
)}
</div>
<PushChanges
key={pushChangesKey(repo, shownCommits)}
repo={repo}
commits={shownCommits}
onShowAll={shownCommit >= 0 && (commits?.length ?? 0) > 1 ? () => setShownCommit(-1) : undefined}
/>
{commits?.length && !error ? (
<SplitGroup
orientation="vertical"
id="vcs-push-split"
className={styles.pushSplit}
defaultLayout={pushSplit}
onLayoutChanged={keepPushSplit}
>
<SplitPanel id="commits" minSize="72px" defaultSize="35%" className={styles.pushSplitPanel}>
{commitList}
</SplitPanel>
<SplitHandle />
<SplitPanel id="changes" minSize="160px" className={styles.pushSplitPanel}>
<PushChanges
key={pushChangesKey(repo, shownCommits)}
repo={repo}
commits={shownCommits}
onShowAll={shownCommit >= 0 && commits.length > 1 ? () => setShownCommit(-1) : undefined}
/>
</SplitPanel>
</SplitGroup>
) : (
commitList
)}
</Dialog>
);
}
const PUSH_SPLIT_KEY = "openidea.vcs.pushSplit";
/** Heights of the Push dialog's commits and changed files, as the splitter left them. */
function loadPushSplit(): Record<string, number> | undefined {
try {
const v: unknown = JSON.parse(localStorage.getItem(PUSH_SPLIT_KEY) ?? "null");
return v && typeof v === "object" && Object.values(v).every((n) => typeof n === "number")
? (v as Record<string, number>)
: undefined;
} catch {
return undefined;
}
}
function savePushSplit(layout: Record<string, number>) {
try {
localStorage.setItem(PUSH_SPLIT_KEY, JSON.stringify(layout));
} catch {
/* kept for this dialog only */
}
}
const SILENT_FORCE_PUSH_KEY = "openidea.vcs.silentForcePushTargets";
function silentForcePushTargets(): string[] {