Merge pull request #4 from Hona/fix/virtual-timeline-anchor-1778643000
fix(app): anchor virtual timeline to bottom
This commit is contained in:
commit
fba26d1f9c
2 changed files with 22 additions and 1 deletions
|
|
@ -29,7 +29,7 @@ import { previewSelectedLines } from "@opencode-ai/ui/pierre/selection-bridge"
|
||||||
import { Button } from "@opencode-ai/ui/button"
|
import { Button } from "@opencode-ai/ui/button"
|
||||||
import { showToast } from "@opencode-ai/ui/toast"
|
import { showToast } from "@opencode-ai/ui/toast"
|
||||||
import { checksum } from "@opencode-ai/core/util/encode"
|
import { checksum } from "@opencode-ai/core/util/encode"
|
||||||
import { useSearchParams } from "@solidjs/router"
|
import { useLocation, useSearchParams } from "@solidjs/router"
|
||||||
import { NewSessionView, SessionHeader } from "@/components/session"
|
import { NewSessionView, SessionHeader } from "@/components/session"
|
||||||
import { useComments } from "@/context/comments"
|
import { useComments } from "@/context/comments"
|
||||||
import { getSessionPrefetch, SESSION_PREFETCH_TTL } from "@/context/global-sync/session-prefetch"
|
import { getSessionPrefetch, SESSION_PREFETCH_TTL } from "@/context/global-sync/session-prefetch"
|
||||||
|
|
@ -196,6 +196,7 @@ export default function Page() {
|
||||||
const comments = useComments()
|
const comments = useComments()
|
||||||
const terminal = useTerminal()
|
const terminal = useTerminal()
|
||||||
const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>()
|
const [searchParams, setSearchParams] = useSearchParams<{ prompt?: string }>()
|
||||||
|
const location = useLocation()
|
||||||
const { params, sessionKey, tabs, view } = useSessionLayout()
|
const { params, sessionKey, tabs, view } = useSessionLayout()
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
|
|
@ -1719,6 +1720,9 @@ export default function Page() {
|
||||||
onUserScroll={markUserScroll}
|
onUserScroll={markUserScroll}
|
||||||
onHistoryScroll={historyLoader.onScrollerScroll}
|
onHistoryScroll={historyLoader.onScrollerScroll}
|
||||||
onAutoScrollInteraction={autoScroll.handleInteraction}
|
onAutoScrollInteraction={autoScroll.handleInteraction}
|
||||||
|
shouldAnchorBottom={() =>
|
||||||
|
!location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
|
||||||
|
}
|
||||||
centered={centered()}
|
centered={centered()}
|
||||||
setContentRef={(el) => {
|
setContentRef={(el) => {
|
||||||
content = el
|
content = el
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ function sameKeys(a: readonly string[] | undefined, b: readonly string[] | undef
|
||||||
}
|
}
|
||||||
|
|
||||||
const timelineCacheLimit = 16
|
const timelineCacheLimit = 16
|
||||||
|
const timelineFallbackItemSize = 60
|
||||||
const timelineCache = new Map<string, { keys: readonly string[]; cache: VirtualizerHandle["cache"] }>()
|
const timelineCache = new Map<string, { keys: readonly string[]; cache: VirtualizerHandle["cache"] }>()
|
||||||
|
|
||||||
function readTimelineCache(id: string, keys: readonly string[]) {
|
function readTimelineCache(id: string, keys: readonly string[]) {
|
||||||
|
|
@ -451,6 +452,7 @@ export function MessageTimeline(props: {
|
||||||
onUserScroll: () => void
|
onUserScroll: () => void
|
||||||
onHistoryScroll: () => void
|
onHistoryScroll: () => void
|
||||||
onAutoScrollInteraction: (event: MouseEvent) => void
|
onAutoScrollInteraction: (event: MouseEvent) => void
|
||||||
|
shouldAnchorBottom: () => boolean
|
||||||
centered: boolean
|
centered: boolean
|
||||||
setContentRef: (el: HTMLDivElement) => void
|
setContentRef: (el: HTMLDivElement) => void
|
||||||
historyShift: boolean
|
historyShift: boolean
|
||||||
|
|
@ -713,6 +715,18 @@ export function MessageTimeline(props: {
|
||||||
let cacheRowKeys = timelineRowKeys()
|
let cacheRowKeys = timelineRowKeys()
|
||||||
let virtualizerSessionKey = cacheSessionKey
|
let virtualizerSessionKey = cacheSessionKey
|
||||||
let virtualizerRowKeys = cacheRowKeys
|
let virtualizerRowKeys = cacheRowKeys
|
||||||
|
let bottomAnchorSessionKey = ""
|
||||||
|
|
||||||
|
const maybeAnchorBottom = () => {
|
||||||
|
const key = sessionKey()
|
||||||
|
if (bottomAnchorSessionKey === key) return
|
||||||
|
if (!virtualizer) return
|
||||||
|
const keys = timelineRowKeys()
|
||||||
|
if (keys.length === 0) return
|
||||||
|
bottomAnchorSessionKey = key
|
||||||
|
if (!props.shouldAnchorBottom()) return
|
||||||
|
virtualizer.scrollToIndex(keys.length - 1, { align: "end" })
|
||||||
|
}
|
||||||
|
|
||||||
createEffect(
|
createEffect(
|
||||||
on(
|
on(
|
||||||
|
|
@ -724,6 +738,7 @@ export function MessageTimeline(props: {
|
||||||
if (virtualizer) {
|
if (virtualizer) {
|
||||||
virtualizerSessionKey = cacheSessionKey
|
virtualizerSessionKey = cacheSessionKey
|
||||||
virtualizerRowKeys = cacheRowKeys
|
virtualizerRowKeys = cacheRowKeys
|
||||||
|
maybeAnchorBottom()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ defer: true },
|
{ defer: true },
|
||||||
|
|
@ -1669,6 +1684,7 @@ export function MessageTimeline(props: {
|
||||||
<Virtualizer
|
<Virtualizer
|
||||||
data={timelineRowKeys()}
|
data={timelineRowKeys()}
|
||||||
cache={virtualCache()}
|
cache={virtualCache()}
|
||||||
|
itemSize={virtualCache() ? undefined : timelineFallbackItemSize}
|
||||||
scrollRef={root()}
|
scrollRef={root()}
|
||||||
shift={props.historyShift}
|
shift={props.historyShift}
|
||||||
keepMounted={keepMounted()}
|
keepMounted={keepMounted()}
|
||||||
|
|
@ -1681,6 +1697,7 @@ export function MessageTimeline(props: {
|
||||||
virtualizer = handle
|
virtualizer = handle
|
||||||
virtualizerSessionKey = cacheSessionKey
|
virtualizerSessionKey = cacheSessionKey
|
||||||
virtualizerRowKeys = cacheRowKeys
|
virtualizerRowKeys = cacheRowKeys
|
||||||
|
maybeAnchorBottom()
|
||||||
scheduleContentRoot(root())
|
scheduleContentRoot(root())
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue