Files
superwork/vscode/webview-ui/src/codeReader/useChangeNav.ts
T

58 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { ChangeStop } from './diffView'
import type { StickyScrollSettings } from './types'
import { useCallback, useLayoutEffect, useRef, useState } from 'react'
import { nextStop } from './diffView'
import { pageLayout } from './pageLayout'
import { scrollOwner } from './scrollOwner'
import { flash } from './useReveal'
import { stickyInset } from './useStickyScroll'
/** 跳到的那处离筛选栏下沿留多少像素。 */
const MARGIN = 8
/**
* 「上一处 / 下一处」和点总览条的刻度:跳到一处改动。它在折叠着的块里时先展开那几层(expand),渲染好了再把它滚到筛选栏下面
* (钉着外层卡片时在钉着的几行下面,sticky 是 VS Code 的 Sticky Scroll 设置)、闪几下。
* 跳过去以后由 [scrollOwner] 跟住那张卡片。接着走从哪算:跳完之后用户没滚过、也没跳到别处就从刚跳到的那处接着走,
* 否则按视口顶上那块的行找(见 [nextStop]);页面长高时滚动所有者自己滚回去的不算用户滚过。
*/
export function useChangeNav(stops: readonly ChangeStop[], collapsed: ReadonlySet<string>, expand: (ids: string[]) => void, sticky: StickyScrollSettings | undefined): {
move: (step: 1 | -1) => void
goTo: (stop: ChangeStop) => void
} {
const [target, setTarget] = useState<ChangeStop | null>(null)
/** 最近一次跳到的那处,和跳完时页面换过几次地方(见 [ScrollOwner.turn](vscode/webview-ui/src/codeReader/scrollOwner.ts#ScrollOwner.turn)) */
const jumped = useRef<{ id: string, turn: number } | null>(null)
useLayoutEffect(() => {
if (!target)
return
const hidden = target.ancestors.filter(id => collapsed.has(id))
if (hidden.length > 0) {
expand(hidden)
return
}
const locate = (): HTMLElement | null => document.querySelector<HTMLElement>(`.cr-block[data-id="${target.id}"]`)
const card = locate()
if (card) {
pageLayout.settle(card)
const inset = pageLayout.pinnedHeight() + stickyInset(card, sticky) + MARGIN
scrollOwner.reveal(locate, box => box.top - inset)
flash(card)
}
jumped.current = { id: target.id, turn: scrollOwner.turn }
setTarget(null)
}, [target, collapsed, expand, sticky])
const move = useCallback((step: 1 | -1) => {
if (stops.length === 0)
return
const from = jumped.current?.turn === scrollOwner.turn ? jumped.current.id : undefined
const index = from === undefined ? -1 : stops.findIndex(stop => stop.id === from)
const at = index >= 0 ? { index } : { line: pageLayout.anchorAt(window.scrollY + pageLayout.pinnedHeight())?.line }
setTarget(stops[nextStop(stops, at, step)])
}, [stops])
return { move, goTo: setTarget }
}