Keep git history graph sticky
Squashed commit from make-git-graph-sticky-current-change.
This commit is contained in:
parent
395e98f015
commit
596baf55a2
|
|
@ -19,6 +19,14 @@ export type GitHistoryPanelState =
|
|||
const DEFAULT_GIT_HISTORY_PANEL_HEIGHT = 256
|
||||
const MIN_GIT_HISTORY_PANEL_HEIGHT = 96
|
||||
const MAX_GIT_HISTORY_PANEL_HEIGHT = 520
|
||||
const MAX_GIT_HISTORY_PANEL_VIEWPORT_HEIGHT = '33vh'
|
||||
|
||||
type GitHistoryResizeSession = {
|
||||
startY: number
|
||||
startHeight: number
|
||||
previousCursor: string
|
||||
previousUserSelect: string
|
||||
}
|
||||
|
||||
function clampGitHistoryPanelHeight(height: number): number {
|
||||
return Math.min(MAX_GIT_HISTORY_PANEL_HEIGHT, Math.max(MIN_GIT_HISTORY_PANEL_HEIGHT, height))
|
||||
|
|
@ -77,29 +85,14 @@ function GitHistoryRow({
|
|||
const visibleRefs = refs.slice(0, 2)
|
||||
const hiddenRefs = refs.slice(2)
|
||||
const rowTooltip = item.message || item.subject
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'grid min-h-[34px] w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_4.5rem_3.25rem_3.75rem] grid-rows-[auto_auto] items-start gap-x-1.5 px-3 py-1 text-left text-xs transition-colors',
|
||||
canOpenCommit && 'cursor-pointer hover:bg-accent/40 focus-visible:bg-accent/40',
|
||||
!canOpenCommit && 'cursor-default',
|
||||
isBoundaryNode && 'text-muted-foreground'
|
||||
)}
|
||||
title={rowTooltip}
|
||||
aria-disabled={!canOpenCommit}
|
||||
aria-label={
|
||||
canOpenCommit ? `Open commit ${item.displayId ?? item.id}: ${item.subject}` : item.subject
|
||||
}
|
||||
data-testid="git-history-row"
|
||||
tabIndex={canOpenCommit ? undefined : -1}
|
||||
onClick={() => {
|
||||
if (canOpenCommit) {
|
||||
onOpenCommit?.(item)
|
||||
}
|
||||
}}
|
||||
>
|
||||
const rowClassName = cn(
|
||||
'grid min-h-[34px] w-full min-w-0 grid-cols-[auto_minmax(0,1fr)_4.5rem_3.25rem_3.75rem] grid-rows-[auto_auto] items-start gap-x-1.5 px-3 py-1 text-left text-xs transition-colors',
|
||||
canOpenCommit && 'cursor-pointer hover:bg-accent/40 focus-visible:bg-accent/40',
|
||||
!canOpenCommit && 'cursor-default',
|
||||
isBoundaryNode && 'text-muted-foreground'
|
||||
)
|
||||
const rowContent = (
|
||||
<>
|
||||
<div className="row-span-2">
|
||||
<GitHistoryGraphSvg viewModel={viewModel} />
|
||||
</div>
|
||||
|
|
@ -162,6 +155,29 @@ function GitHistoryRow({
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
if (!canOpenCommit) {
|
||||
return (
|
||||
<div className={rowClassName} title={rowTooltip} data-testid="git-history-row">
|
||||
{rowContent}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={rowClassName}
|
||||
title={rowTooltip}
|
||||
aria-label={`Open commit ${item.displayId ?? item.id}: ${item.subject}`}
|
||||
data-testid="git-history-row"
|
||||
onClick={() => {
|
||||
onOpenCommit?.(item)
|
||||
}}
|
||||
>
|
||||
{rowContent}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
|
@ -199,15 +215,16 @@ export function GitHistoryPanel({
|
|||
const loading = state.status === 'loading' || state.status === 'refreshing'
|
||||
const count = result?.items.length ?? 0
|
||||
const [panelHeight, setPanelHeight] = useState(DEFAULT_GIT_HISTORY_PANEL_HEIGHT)
|
||||
const resizeSessionRef = useRef<{ startY: number; startHeight: number } | null>(null)
|
||||
const resizeSessionRef = useRef<GitHistoryResizeSession | null>(null)
|
||||
|
||||
const stopResize = useCallback((): void => {
|
||||
if (!resizeSessionRef.current) {
|
||||
const session = resizeSessionRef.current
|
||||
if (!session) {
|
||||
return
|
||||
}
|
||||
resizeSessionRef.current = null
|
||||
document.body.style.cursor = ''
|
||||
document.body.style.userSelect = ''
|
||||
document.body.style.cursor = session.previousCursor
|
||||
document.body.style.userSelect = session.previousUserSelect
|
||||
}, [])
|
||||
|
||||
const handleResizePointerMove = useCallback((event: PointerEvent): void => {
|
||||
|
|
@ -228,6 +245,7 @@ export function GitHistoryPanel({
|
|||
window.removeEventListener('pointerup', stopResize)
|
||||
window.removeEventListener('pointercancel', stopResize)
|
||||
window.removeEventListener('blur', stopResize)
|
||||
stopResize()
|
||||
}
|
||||
}, [handleResizePointerMove, stopResize])
|
||||
|
||||
|
|
@ -237,7 +255,12 @@ export function GitHistoryPanel({
|
|||
return
|
||||
}
|
||||
event.preventDefault()
|
||||
resizeSessionRef.current = { startY: event.clientY, startHeight: panelHeight }
|
||||
resizeSessionRef.current = {
|
||||
startY: event.clientY,
|
||||
startHeight: panelHeight,
|
||||
previousCursor: document.body.style.cursor,
|
||||
previousUserSelect: document.body.style.userSelect
|
||||
}
|
||||
document.body.style.cursor = 'row-resize'
|
||||
document.body.style.userSelect = 'none'
|
||||
event.currentTarget.setPointerCapture(event.pointerId)
|
||||
|
|
@ -263,7 +286,9 @@ export function GitHistoryPanel({
|
|||
}, [])
|
||||
|
||||
const expandedBodyClassName = 'overflow-y-auto scrollbar-sleek'
|
||||
const expandedBodyStyle = { height: panelHeight }
|
||||
const expandedBodyStyle = {
|
||||
height: `min(${panelHeight}px, ${MAX_GIT_HISTORY_PANEL_VIEWPORT_HEIGHT})`
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
|
|
|
|||
|
|
@ -1285,6 +1285,8 @@ function SourceControlInner(): React.JSX.Element {
|
|||
}, [entries])
|
||||
|
||||
const normalizedFilter = filterQuery.toLowerCase()
|
||||
const isGitHistoryVisible =
|
||||
scope === 'all' && !normalizedFilter && Boolean(activeWorktreeId && worktreePath && !isFolder)
|
||||
|
||||
const filteredGrouped = useMemo(() => {
|
||||
if (!normalizedFilter) {
|
||||
|
|
@ -2955,7 +2957,8 @@ function SourceControlInner(): React.JSX.Element {
|
|||
!worktreePath ||
|
||||
isFolder ||
|
||||
!isBranchVisible ||
|
||||
!isGitHistoryExpanded
|
||||
!isGitHistoryExpanded ||
|
||||
!isGitHistoryVisible
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
|
@ -3010,6 +3013,7 @@ function SourceControlInner(): React.JSX.Element {
|
|||
isBranchVisible,
|
||||
isFolder,
|
||||
isGitHistoryExpanded,
|
||||
isGitHistoryVisible,
|
||||
worktreePath
|
||||
])
|
||||
|
||||
|
|
@ -3033,7 +3037,7 @@ function SourceControlInner(): React.JSX.Element {
|
|||
useEffect(() => {
|
||||
// Why: history shells out to git. Defer the first load until the user
|
||||
// expands Graph so source control stays cheap for large/remote repos.
|
||||
if (!isBranchVisible || !isGitHistoryExpanded) {
|
||||
if (!isBranchVisible || !isGitHistoryExpanded || !isGitHistoryVisible) {
|
||||
return
|
||||
}
|
||||
void refreshGitHistoryRef.current()
|
||||
|
|
@ -3043,6 +3047,7 @@ function SourceControlInner(): React.JSX.Element {
|
|||
isBranchVisible,
|
||||
isFolder,
|
||||
isGitHistoryExpanded,
|
||||
isGitHistoryVisible,
|
||||
worktreePath
|
||||
])
|
||||
|
||||
|
|
@ -4136,24 +4141,20 @@ function SourceControlInner(): React.JSX.Element {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{scope === 'all' &&
|
||||
!normalizedFilter &&
|
||||
activeWorktreeId &&
|
||||
worktreePath &&
|
||||
!isFolder && (
|
||||
// Why: the graph is reference context for the whole panel, so when
|
||||
// file sections are short it should occupy the bottom, and when the
|
||||
// pane scrolls it should remain docked as branch context.
|
||||
<div className="sticky bottom-0 z-10 mt-auto shrink-0 border-t border-border bg-sidebar/95 backdrop-blur-sm">
|
||||
<GitHistoryPanel
|
||||
state={gitHistoryState}
|
||||
collapsed={collapsedSections.has('history')}
|
||||
onToggle={() => toggleSection('history')}
|
||||
onRefresh={() => void refreshGitHistory()}
|
||||
onOpenCommit={(item) => void openHistoryCommitDiff(item)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{isGitHistoryVisible && (
|
||||
// Why: the graph is reference context for the whole panel, so when
|
||||
// file sections are short it should occupy the bottom, and when the
|
||||
// pane scrolls it should remain docked as branch context.
|
||||
<div className="sticky bottom-0 z-10 mt-auto shrink-0 border-t border-border bg-sidebar/95 backdrop-blur-sm">
|
||||
<GitHistoryPanel
|
||||
state={gitHistoryState}
|
||||
collapsed={collapsedSections.has('history')}
|
||||
onToggle={() => toggleSection('history')}
|
||||
onRefresh={() => void refreshGitHistory()}
|
||||
onOpenCommit={(item) => void openHistoryCommitDiff(item)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{selectedKeys.size > 0 && (
|
||||
|
|
|
|||
Loading…
Reference in New Issue