Browse Source

refac: onScroll -> IntersectionObserver for infinite scroll

Timothy J. Baek 9 months ago
parent
commit
4441338574
2 changed files with 56 additions and 36 deletions
  1. 55 36
      src/lib/components/layout/Sidebar.svelte
  2. 1 0
      src/lib/stores/index.ts

+ 55 - 36
src/lib/components/layout/Sidebar.svelte

@@ -39,7 +39,7 @@
 	import UserMenu from './Sidebar/UserMenu.svelte';
 	import ChatItem from './Sidebar/ChatItem.svelte';
 	import DeleteConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
-	import Sparkles from '../icons/Sparkles.svelte';
+	import Spinner from '../common/Spinner.svelte';
 
 	const BREAKPOINT = 768;
 
@@ -58,10 +58,8 @@
 	let paginationScrollThreashold = 0.6;
 	let nextPageLoading = false;
 	let chatPagniationComplete = false;
-	// number of chats per page depends on screen size.
-	// 35px is the height of each chat item.
-	// load 5 extra chats
-	pageLimit.set(Math.round(window.innerHeight / 35) + 5);
+
+	pageLimit.set(20);
 
 	$: filteredChatList = $chats.filter((chat) => {
 		if (search === '') {
@@ -153,6 +151,48 @@
 		window.addEventListener('focus', onFocus);
 		window.addEventListener('blur', onBlur);
 
+		// Infinite scroll
+		const loader = document.getElementById('loader');
+
+		const observer = new IntersectionObserver(
+			(entries, observer) => {
+				entries.forEach((entry) => {
+					if (entry.isIntersecting) {
+						loadMoreContent();
+						observer.unobserve(loader); // Stop observing until content is loaded
+					}
+				});
+			},
+			{
+				root: null, // viewport
+				rootMargin: '0px',
+				threshold: 1.0 // When 100% of the loader is visible
+			}
+		);
+
+		observer.observe(loader);
+		const loadMoreContent = async () => {
+			if (!$scrollPaginationEnabled) return;
+			if ($tagView) return;
+			if (nextPageLoading) return;
+			if (chatPagniationComplete) return;
+
+			nextPageLoading = true;
+			pageSkip.set($pageSkip + 1);
+			// extend existing chats
+			const nextPageChats = await getChatList(
+				localStorage.token,
+				$pageSkip * $pageLimit,
+				$pageLimit
+			);
+			// once the bottom of the list has been reached (no results) there is no need to continue querying
+			chatPagniationComplete = nextPageChats.length === 0;
+			await chats.set([...$chats, ...nextPageChats]);
+			nextPageLoading = false;
+
+			observer.observe(loader); // Start observing again after content is loaded
+		};
+
 		return () => {
 			window.removeEventListener('keydown', onKeyDown);
 			window.removeEventListener('keyup', onKeyUp);
@@ -427,8 +467,8 @@
 						bind:value={search}
 						on:focus={async () => {
 							disablePagination();
+							// TODO: migrate backend for more scalable search mechanism
 							await chats.set(await getChatList(localStorage.token)); // when searching, load all chats
-
 							enrichChatsWithContent($chats);
 						}}
 					/>
@@ -506,33 +546,7 @@
 				</div>
 			{/if}
 
-			<div
-				class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto scrollbar-hidden"
-				on:scroll={async (e) => {
-					if (!$scrollPaginationEnabled) return;
-					if ($tagView) return;
-					if (nextPageLoading) return;
-					if (chatPagniationComplete) return;
-
-					const maxScroll = e.target.scrollHeight - e.target.clientHeight;
-					const currentPos = e.target.scrollTop;
-					const ratio = currentPos / maxScroll;
-					if (ratio >= paginationScrollThreashold) {
-						nextPageLoading = true;
-						pageSkip.set($pageSkip + 1);
-						// extend existing chats
-						const nextPageChats = await getChatList(
-							localStorage.token,
-							$pageSkip * $pageLimit,
-							$pageLimit
-						);
-						// once the bottom of the list has been reached (no results) there is no need to continue querying
-						chatPagniationComplete = nextPageChats.length === 0;
-						await chats.set([...$chats, ...nextPageChats]);
-						nextPageLoading = false;
-					}
-				}}
-			>
+			<div class="pl-2 my-2 flex-1 flex flex-col space-y-1 overflow-y-auto scrollbar-hidden">
 				{#each filteredChatList as chat, idx}
 					{#if idx === 0 || (idx > 0 && chat.time_range !== filteredChatList[idx - 1].time_range)}
 						<div
@@ -582,9 +596,14 @@
 						}}
 					/>
 				{/each}
-				{#if nextPageLoading}
-					<div class="w-full flex justify-center py-4 animate-pulse">
-						<Sparkles />
+
+				{#if !chatPagniationComplete}
+					<div
+						id="loader"
+						class="w-full flex justify-center py-1 text-xs animate-pulse items-center gap-2"
+					>
+						<Spinner className=" size-4" />
+						<div class=" ">Loading...</div>
 					</div>
 				{/if}
 			</div>

+ 1 - 0
src/lib/stores/index.ts

@@ -41,6 +41,7 @@ export const showSettings = writable(false);
 export const showArchivedChats = writable(false);
 export const showChangelog = writable(false);
 export const showCallOverlay = writable(false);
+
 export const scrollPaginationEnabled = writable(true);
 export const pageSkip = writable(0);
 export const pageLimit = writable(-1);