1
0
Fork 0
forked from Mirrors/gomuks

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

View file

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

View file

@ -5,4 +5,15 @@ div.timeline-view {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; 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 // 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/>. // 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 { useRoomTimeline } from "@/api/statestore"
import { MemDBEvent } from "@/api/types" import { MemDBEvent } from "@/api/types"
import useFocus from "@/util/focus.ts" import useFocus from "@/util/focus.ts"
@ -27,14 +28,18 @@ const TimelineView = () => {
const room = roomCtx.store const room = roomCtx.store
const timeline = useRoomTimeline(room) const timeline = useRoomTimeline(room)
const client = use(ClientContext)! const client = use(ClientContext)!
const [isLoadingHistory, setLoadingHistory] = useState(false)
const loadHistory = useCallback(() => { const loadHistory = useCallback(() => {
setLoadingHistory(true)
client.loadMoreHistory(room.roomID) client.loadMoreHistory(room.roomID)
.catch(err => console.error("Failed to load history", err)) .catch(err => console.error("Failed to load history", err))
.finally(() => setLoadingHistory(false))
}, [client, room]) }, [client, room])
const bottomRef = roomCtx.timelineBottomRef const bottomRef = roomCtx.timelineBottomRef
const topRef = useRef<HTMLDivElement>(null) const topRef = useRef<HTMLDivElement>(null)
const timelineViewRef = useRef<HTMLDivElement>(null) const timelineViewRef = useRef<HTMLDivElement>(null)
const prevOldestTimelineRow = useRef(0) const prevOldestTimelineRow = useRef(0)
const oldestTimelineRow = timeline[0]?.timeline_rowid
const oldScrollHeight = useRef(0) const oldScrollHeight = useRef(0)
const focused = useFocus() const focused = useFocus()
@ -93,7 +98,7 @@ const TimelineView = () => {
}, [focused, client, roomCtx, room, timeline]) }, [focused, client, roomCtx, room, timeline])
useEffect(() => { useEffect(() => {
const topElem = topRef.current const topElem = topRef.current
if (!topElem) { if (!topElem || !room.hasMoreHistory) {
return return
} }
const observer = new IntersectionObserver(entries => { const observer = new IntersectionObserver(entries => {
@ -108,12 +113,14 @@ const TimelineView = () => {
}) })
observer.observe(topElem) observer.observe(topElem)
return () => observer.unobserve(topElem) return () => observer.unobserve(topElem)
}, [room, loadHistory]) }, [room, room.hasMoreHistory, loadHistory, oldestTimelineRow])
let prevEvt: MemDBEvent | null = null let prevEvt: MemDBEvent | null = null
return <div className="timeline-view" onScroll={handleScroll} ref={timelineViewRef}> return <div className="timeline-view" onScroll={handleScroll} ref={timelineViewRef}>
<div className="timeline-beginning"> <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>
<div className="timeline-list"> <div className="timeline-list">
<div className="timeline-top-ref" ref={topRef}/> <div className="timeline-top-ref" ref={topRef}/>