diff --git a/web/src/api/client.ts b/web/src/api/client.ts index 8c3c9fa..8d54254 100644 --- a/web/src/api/client.ts +++ b/web/src/api/client.ts @@ -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 diff --git a/web/src/api/statestore/room.ts b/web/src/api/statestore/room.ts index 50e9f3f..4ab42b4 100644 --- a/web/src/api/statestore/room.ts +++ b/web/src/api/statestore/room.ts @@ -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() diff --git a/web/src/ui/timeline/TimelineView.css b/web/src/ui/timeline/TimelineView.css index d676b18..9620bc0 100644 --- a/web/src/ui/timeline/TimelineView.css +++ b/web/src/ui/timeline/TimelineView.css @@ -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; + } + } } diff --git a/web/src/ui/timeline/TimelineView.tsx b/web/src/ui/timeline/TimelineView.tsx index 2021679..622f558 100644 --- a/web/src/ui/timeline/TimelineView.tsx +++ b/web/src/ui/timeline/TimelineView.tsx @@ -13,7 +13,8 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -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(null) const timelineViewRef = useRef(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
- + {room.hasMoreHistory ? : "No more history available in this room"}