mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
Extract PR file state into entry-scoped hooks
Replace manual state resets with custom hooks that automatically clear section heights and active section when switching PR entries. This prevents state leakage between files and simplifies the diff viewer component. Also validates the active section key exists before passing it to child components.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export function usePRFileActiveSection(
|
||||
entrySignature: string
|
||||
): [string | null, (key: string | null) => void] {
|
||||
const [state, setState] = useState({ entrySignature, key: null as string | null })
|
||||
const setActiveSection = useCallback(
|
||||
(key: string | null) => setState({ entrySignature, key }),
|
||||
[entrySignature]
|
||||
)
|
||||
return [state.entrySignature === entrySignature ? state.key : null, setActiveSection]
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
/* oxlint-disable react-doctor/no-adjust-state-on-prop-change -- Why: switching PRs re-seeds the whole diff view (sections, heights, scroll target, cached loaded set) from the persisted cache, which cannot be derived during render. */
|
||||
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
import type { editor as monacoEditor } from 'monaco-editor'
|
||||
@@ -34,6 +33,8 @@ import { addPullRequestLineComment } from './line-comment'
|
||||
import { usePRFileSectionLoader } from './section-loader'
|
||||
import { usePRFilesDiffViewPersistence } from './view-restore'
|
||||
import { buildInlineReviewComments } from './inline-comments'
|
||||
import { usePRFileSectionHeights } from './section-heights'
|
||||
import { usePRFileActiveSection } from './active-section'
|
||||
|
||||
export function PRFilesCombinedDiffViewer({
|
||||
files,
|
||||
@@ -106,8 +107,8 @@ export function PRFilesCombinedDiffViewer({
|
||||
const [sections, setSections] = useState<DiffSection[]>([])
|
||||
const [sideBySide, setSideBySide] = useState(false)
|
||||
const [fileTreeCollapsed, setFileTreeCollapsed] = useState(false)
|
||||
const [sectionHeights, setSectionHeights] = useState<Record<number, number>>({})
|
||||
const [activeTreeSectionKey, setActiveTreeSectionKey] = useState<string | null>(null)
|
||||
const [sectionHeights, setSectionHeights] = usePRFileSectionHeights(entrySignature)
|
||||
const [activeTreeSectionKey, setActiveTreeSectionKey] = usePRFileActiveSection(entrySignature)
|
||||
// Why: short virtualizer-key token; entrySignature serializes every file and would be copied into every item key.
|
||||
const [entryRevision, setEntryRevision] = useState(0)
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null)
|
||||
@@ -147,8 +148,6 @@ export function PRFilesCombinedDiffViewer({
|
||||
loadedIndicesRef.current.clear()
|
||||
loadingIndicesRef.current.clear()
|
||||
pendingRestoreScrollTopRef.current = prFilesDiffScrollTopCache.get(viewStateKey) ?? null
|
||||
setSectionHeights({})
|
||||
setActiveTreeSectionKey(null)
|
||||
setSections(
|
||||
entries.map((entry) => ({
|
||||
key: getPRFileSectionKey(entry.path),
|
||||
@@ -167,7 +166,7 @@ export function PRFilesCombinedDiffViewer({
|
||||
largeDiffRenderLimit: null
|
||||
}))
|
||||
)
|
||||
}, [entries, entrySignature, viewStateKey])
|
||||
}, [entries, entrySignature, setActiveTreeSectionKey, setSectionHeights, viewStateKey])
|
||||
|
||||
const { loadSection, retrySection, toggleSection, setAllSectionsCollapsed } =
|
||||
usePRFileSectionLoader({
|
||||
@@ -189,6 +188,10 @@ export function PRFilesCombinedDiffViewer({
|
||||
|
||||
const allSectionsCollapsed = sections.length > 0 && sections.every((section) => section.collapsed)
|
||||
const sectionIndexByKey = useMemo(() => createCombinedDiffSectionIndexMap(sections), [sections])
|
||||
const visibleActiveTreeSectionKey =
|
||||
activeTreeSectionKey && sectionIndexByKey.has(activeTreeSectionKey)
|
||||
? activeTreeSectionKey
|
||||
: null
|
||||
const viewedSectionKeys = useMemo(
|
||||
() => new Set(files.filter(isPRFileViewed).map((file) => getPRFileSectionKey(file.path))),
|
||||
[files]
|
||||
@@ -237,7 +240,7 @@ export function PRFilesCombinedDiffViewer({
|
||||
sectionHeights,
|
||||
sideBySide,
|
||||
fileTreeCollapsed,
|
||||
activeTreeSectionKey,
|
||||
activeTreeSectionKey: visibleActiveTreeSectionKey,
|
||||
loadedIndicesRef,
|
||||
scrollContainerRef,
|
||||
pendingRestoreScrollTopRef
|
||||
@@ -257,7 +260,7 @@ export function PRFilesCombinedDiffViewer({
|
||||
setActiveTreeSectionKey(sectionsRef.current[navigatedIndex]?.key ?? null)
|
||||
}
|
||||
},
|
||||
[sectionIndexByKey, toggleSection, virtualizer]
|
||||
[sectionIndexByKey, setActiveTreeSectionKey, toggleSection, virtualizer]
|
||||
)
|
||||
|
||||
const openFilesOnGitHub = useCallback(() => {
|
||||
@@ -334,7 +337,7 @@ export function PRFilesCombinedDiffViewer({
|
||||
worktreePath={repoPath}
|
||||
entries={entries}
|
||||
sectionIndexByKey={sectionIndexByKey}
|
||||
activeSectionKey={activeTreeSectionKey}
|
||||
activeSectionKey={visibleActiveTreeSectionKey}
|
||||
viewedSectionKeys={viewedSectionKeys}
|
||||
collapsed={fileTreeCollapsed}
|
||||
onCollapsedChange={setFileTreeCollapsed}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DecoratedDiffComment } from '@/components/diff-comments/useDiffCommentDecorator'
|
||||
import type { DecoratedDiffComment } from '@/components/diff-comments/decorated-diff-comment'
|
||||
import { formatRelativeTime } from '@/components/github/work-item-state-presentation'
|
||||
import type { PRComment } from '../../../../../shared/github/comment-types'
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useCallback, useState, type SetStateAction } from 'react'
|
||||
|
||||
const EMPTY_SECTION_HEIGHTS: Record<number, number> = {}
|
||||
|
||||
export function usePRFileSectionHeights(
|
||||
entrySignature: string
|
||||
): [Record<number, number>, (updater: SetStateAction<Record<number, number>>) => void] {
|
||||
const [state, setState] = useState(() => ({ entrySignature, values: EMPTY_SECTION_HEIGHTS }))
|
||||
const heights = state.entrySignature === entrySignature ? state.values : EMPTY_SECTION_HEIGHTS
|
||||
const setHeights = useCallback(
|
||||
(updater: SetStateAction<Record<number, number>>) => {
|
||||
setState((prev) => {
|
||||
const current = prev.entrySignature === entrySignature ? prev.values : EMPTY_SECTION_HEIGHTS
|
||||
return {
|
||||
entrySignature,
|
||||
values: typeof updater === 'function' ? updater(current) : updater
|
||||
}
|
||||
})
|
||||
},
|
||||
[entrySignature]
|
||||
)
|
||||
return [heights, setHeights]
|
||||
}
|
||||
Reference in New Issue
Block a user