mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 06:45:22 +00:00
wip: scroll position
This commit is contained in:
@@ -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>
|
||||
)
|
||||
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
Reference in New Issue
Block a user