web/timeline: auto-load history until screen is full

This commit is contained in:
Tulir Asokan 2024-11-26 22:54:54 +02:00
parent 05f64edeaf
commit 83a4df9375
4 changed files with 27 additions and 7 deletions

View file

@ -274,9 +274,8 @@ export default class Client {
const room = this.store.rooms.get(roomID)
if (!room) {
throw new Error("Room not found")
}
if (room.paginating) {
return
} else if (room.paginating) {
throw new Error("Already paginating")
}
room.paginating = true
try {
@ -288,6 +287,7 @@ export default class Client {
if (room.timeline[0]?.timeline_rowid !== oldestRowID) {
throw new Error("Timeline changed while loading history")
}
room.hasMoreHistory = resp.has_more
room.applyPagination(resp.events)
} finally {
room.paginating = false

View file

@ -112,6 +112,7 @@ export class RoomStateStore {
paginating = false
paginationRequestedForRow = -1
readUpToRow = -1
hasMoreHistory = true
constructor(meta: DBRoom, private parent: StateStore) {
this.roomID = meta.room_id
@ -467,6 +468,7 @@ export class RoomStateStore {
this.#membersCache = null
this.#autocompleteMembersCache = null
this.paginationRequestedForRow = -1
this.hasMoreHistory = true
this.timeline = []
this.notifyTimelineSubscribers()
const eventsToKeepList = this.eventsByRowID.values()

View file

@ -5,4 +5,15 @@ div.timeline-view {
display: flex;
flex-direction: column;
justify-content: space-between;
> div.timeline-beginning {
display: flex;
justify-content: space-around;
> button {
display: flex;
padding: .5rem 1rem;
gap: .5rem;
}
}
}

View file

@ -13,7 +13,8 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { use, useCallback, useEffect, useLayoutEffect, useRef } from "react"
import { use, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react"
import { ScaleLoader } from "react-spinners"
import { useRoomTimeline } from "@/api/statestore"
import { MemDBEvent } from "@/api/types"
import useFocus from "@/util/focus.ts"
@ -27,14 +28,18 @@ const TimelineView = () => {
const room = roomCtx.store
const timeline = useRoomTimeline(room)
const client = use(ClientContext)!
const [isLoadingHistory, setLoadingHistory] = useState(false)
const loadHistory = useCallback(() => {
setLoadingHistory(true)
client.loadMoreHistory(room.roomID)
.catch(err => console.error("Failed to load history", err))
.finally(() => setLoadingHistory(false))
}, [client, room])
const bottomRef = roomCtx.timelineBottomRef
const topRef = useRef<HTMLDivElement>(null)
const timelineViewRef = useRef<HTMLDivElement>(null)
const prevOldestTimelineRow = useRef(0)
const oldestTimelineRow = timeline[0]?.timeline_rowid
const oldScrollHeight = useRef(0)
const focused = useFocus()
@ -93,7 +98,7 @@ const TimelineView = () => {
}, [focused, client, roomCtx, room, timeline])
useEffect(() => {
const topElem = topRef.current
if (!topElem) {
if (!topElem || !room.hasMoreHistory) {
return
}
const observer = new IntersectionObserver(entries => {
@ -108,12 +113,14 @@ const TimelineView = () => {
})
observer.observe(topElem)
return () => observer.unobserve(topElem)
}, [room, loadHistory])
}, [room, room.hasMoreHistory, loadHistory, oldestTimelineRow])
let prevEvt: MemDBEvent | null = null
return <div className="timeline-view" onScroll={handleScroll} ref={timelineViewRef}>
<div className="timeline-beginning">
<button onClick={loadHistory}>Load history</button>
{room.hasMoreHistory ? <button onClick={loadHistory} disabled={isLoadingHistory}>
{isLoadingHistory ? <><ScaleLoader /> Loading history...</> : "Load more history"}
</button> : "No more history available in this room"}
</div>
<div className="timeline-list">
<div className="timeline-top-ref" ref={topRef}/>