wip: scroll position

This commit is contained in:
Adam
2026-03-11 11:17:29 -05:00
parent c86aa40efd
commit e7e17dbc90
3 changed files with 84 additions and 99 deletions

View File

@@ -862,6 +862,36 @@ export default function Page() {
</div>
)
const reviewEmpty = (input: { loadingClass: string; emptyClass: string }) => {
if (store.changes === "turn") return emptyTurn()
if (hasReview() && !diffsReady()) {
return <div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div>
}
if (reviewEmptyKey() === "session.review.noVcs") {
return (
<div class={input.emptyClass}>
<div class="flex flex-col gap-3">
<div class="text-14-medium text-text-strong">Create a Git repository</div>
<div class="text-14-regular text-text-base max-w-md" style={{ "line-height": "var(--line-height-normal)" }}>
Track, review, and undo changes in this project
</div>
</div>
<Button size="large" disabled={ui.git} onClick={initGit}>
{ui.git ? "Creating Git repository..." : "Create Git repository"}
</Button>
</div>
)
}
return (
<div class={input.emptyClass}>
<div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div>
</div>
)
}
const reviewContent = (input: {
diffStyle: DiffStyle
onDiffStyleChange?: (style: DiffStyle) => void
@@ -870,98 +900,25 @@ export default function Page() {
emptyClass: string
}) => (
<Show when={!store.deferRender}>
<Switch>
<Match when={store.changes === "turn" && !!params.id}>
<SessionReviewTab
title={changesTitle()}
empty={emptyTurn()}
diffs={reviewDiffs}
view={view}
diffStyle={input.diffStyle}
onDiffStyleChange={input.onDiffStyleChange}
onScrollRef={(el) => setTree("reviewScroll", el)}
focusedFile={tree.activeDiff}
onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })}
onLineCommentUpdate={updateCommentInContext}
onLineCommentDelete={removeCommentFromContext}
lineCommentActions={reviewCommentActions()}
comments={comments.all()}
focusedComment={comments.focus()}
onFocusedCommentChange={comments.setFocus}
onViewFile={openReviewFile}
classes={input.classes}
/>
</Match>
<Match when={hasReview()}>
<Show
when={diffsReady()}
fallback={<div class={input.loadingClass}>{language.t("session.review.loadingChanges")}</div>}
>
<SessionReviewTab
title={changesTitle()}
diffs={reviewDiffs}
view={view}
diffStyle={input.diffStyle}
onDiffStyleChange={input.onDiffStyleChange}
onScrollRef={(el) => setTree("reviewScroll", el)}
focusedFile={tree.activeDiff}
onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })}
onLineCommentUpdate={updateCommentInContext}
onLineCommentDelete={removeCommentFromContext}
lineCommentActions={reviewCommentActions()}
comments={comments.all()}
focusedComment={comments.focus()}
onFocusedCommentChange={comments.setFocus}
onViewFile={openReviewFile}
classes={input.classes}
/>
</Show>
</Match>
<Match when={true}>
<SessionReviewTab
title={changesTitle()}
empty={
store.changes === "turn" ? (
emptyTurn()
) : reviewEmptyKey() === "session.review.noVcs" ? (
<div class={input.emptyClass}>
<div class="flex flex-col gap-3">
<div class="text-14-medium text-text-strong">Create a Git repository</div>
<div
class="text-14-regular text-text-base max-w-md"
style={{ "line-height": "var(--line-height-normal)" }}
>
Track, review, and undo changes in this project
</div>
</div>
<Button size="large" disabled={ui.git} onClick={initGit}>
{ui.git ? "Creating Git repository..." : "Create Git repository"}
</Button>
</div>
) : (
<div class={input.emptyClass}>
<div class="text-14-regular text-text-weak max-w-56">{language.t(reviewEmptyKey())}</div>
</div>
)
}
diffs={reviewDiffs}
view={view}
diffStyle={input.diffStyle}
onDiffStyleChange={input.onDiffStyleChange}
onScrollRef={(el) => setTree("reviewScroll", el)}
focusedFile={tree.activeDiff}
onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })}
onLineCommentUpdate={updateCommentInContext}
onLineCommentDelete={removeCommentFromContext}
lineCommentActions={reviewCommentActions()}
comments={comments.all()}
focusedComment={comments.focus()}
onFocusedCommentChange={comments.setFocus}
onViewFile={openReviewFile}
classes={input.classes}
/>
</Match>
</Switch>
<SessionReviewTab
title={changesTitle()}
empty={reviewEmpty(input)}
diffs={reviewDiffs}
view={view}
diffStyle={input.diffStyle}
onDiffStyleChange={input.onDiffStyleChange}
onScrollRef={(el) => setTree("reviewScroll", el)}
focusedFile={tree.activeDiff}
onLineComment={(comment) => addCommentToContext({ ...comment, origin: "review" })}
onLineCommentUpdate={updateCommentInContext}
onLineCommentDelete={removeCommentFromContext}
lineCommentActions={reviewCommentActions()}
comments={comments.all()}
focusedComment={comments.focus()}
onFocusedCommentChange={comments.setFocus}
onViewFile={openReviewFile}
classes={input.classes}
/>
</Show>
)

View File

@@ -491,6 +491,29 @@ function renderViewer<I extends RenderTarget>(opts: {
opts.onReady()
}
function preserve(viewer: Viewer) {
const root = scrollParent(viewer.wrapper)
if (!root) return () => {}
const high = viewer.container.getBoundingClientRect().height
if (!high) return () => {}
const top = viewer.wrapper.getBoundingClientRect().top - root.getBoundingClientRect().top
const prev = viewer.container.style.minHeight
viewer.container.style.minHeight = `${Math.ceil(high)}px`
let done = false
return () => {
if (done) return
done = true
viewer.container.style.minHeight = prev
const next = viewer.wrapper.getBoundingClientRect().top - root.getBoundingClientRect().top
const delta = next - top
if (delta) root.scrollTop += delta
}
}
function scrollParent(el: HTMLElement): HTMLElement | undefined {
let parent = el.parentElement
while (parent) {
@@ -990,12 +1013,13 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
return { ...perf, disableLineNumbers: true }
})
const notify = () => {
const notify = (done?: VoidFunction) => {
notifyRendered({
viewer,
isReady: (root) => root.querySelector("[data-line]") != null,
settleFrames: 1,
onReady: () => {
done?.()
setSelectedLines(viewer.lastSelection)
viewer.find.refresh({ reset: true })
local.onRendered?.()
@@ -1016,6 +1040,9 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
const virtualizer = virtuals.get()
const beforeContents = typeof local.before?.contents === "string" ? local.before.contents : ""
const afterContents = typeof local.after?.contents === "string" ? local.after.contents : ""
const done = preserve(viewer)
onCleanup(done)
const cacheKey = (contents: string) => {
if (!large()) return sampledChecksum(contents, contents.length)
@@ -1040,7 +1067,7 @@ function DiffViewer<T>(props: DiffFileProps<T>) {
containerWrapper: viewer.container,
})
},
onReady: notify,
onReady: () => notify(done),
})
})

View File

@@ -60,6 +60,8 @@ export type SessionReviewCommentActions = {
export type SessionReviewFocus = { file: string; id: string }
type ReviewDiff = FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> }
export interface SessionReviewProps {
title?: JSX.Element
empty?: JSX.Element
@@ -83,7 +85,7 @@ export interface SessionReviewProps {
classList?: Record<string, boolean | undefined>
classes?: { root?: string; header?: string; container?: string }
actions?: JSX.Element
diffs: (FileDiff & { preloaded?: PreloadMultiFileDiffResult<any> })[]
diffs: ReviewDiff[]
onViewFile?: (file: string) => void
readFile?: (path: string) => Promise<FileContent | undefined>
}
@@ -146,8 +148,8 @@ export const SessionReview = (props: SessionReviewProps) => {
const [opened, setOpened] = createSignal<SessionReviewFocus | null>(null)
const open = () => props.open ?? store.open
const files = createMemo(() => props.diffs.map((d) => d.file))
const diffs = createMemo(() => new Map(props.diffs.map((d) => [d.file, d] as const)))
const files = createMemo(() => props.diffs.map((diff) => diff.file))
const diffs = createMemo(() => new Map(props.diffs.map((diff) => [diff.file, diff] as const)))
const diffStyle = () => props.diffStyle ?? (props.split ? "split" : "unified")
const hasDiffs = () => files().length > 0
@@ -282,8 +284,7 @@ export const SessionReview = (props: SessionReviewProps) => {
{(file) => {
let wrapper: HTMLDivElement | undefined
const diff = createMemo(() => diffs().get(file))
const item = () => diff()!
const item = createMemo(() => diffs().get(file)!)
const expanded = createMemo(() => open().includes(file))
const force = () => !!store.force[file]