Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // Infinite Scroll Observer Component
- <script lang="ts">
- import type { OnChanged, PaginatedData } from '$lib/types';
- import AppInfiniteProgress from '../state/AppInfiniteProgress.svelte';
- type InfiniteScrollProps = {
- threshold?: number;
- data: PaginatedData<unknown>;
- onloadmore: OnChanged<number>;
- };
- let { threshold = 40, data, onloadmore }: InfiniteScrollProps = $props();
- let isLoading = $derived(data.isLoading);
- let page = $derived(data.page);
- let pages = $derived(data.pageCount);
- let scrollY = $state(0);
- let innerHeight = $state(0);
- let innerWidth = $state(0);
- let lastScrollPosition = 0;
- function onscroll(e: Event) {
- const documentHeight = Math.max(
- document.body.scrollHeight,
- document.body.offsetHeight,
- document.documentElement.clientHeight,
- document.documentElement.scrollHeight,
- document.documentElement.offsetHeight
- );
- const maxScrollY = documentHeight - innerHeight;
- const position = scrollY;
- const offset = maxScrollY - scrollY;
- if (position < lastScrollPosition) {
- lastScrollPosition = position;
- return;
- }
- lastScrollPosition = position;
- if (!data.hasNext || offset > threshold || isLoading || page >= pages) return;
- onloadmore(page);
- }
- </script>
- <svelte:window bind:scrollY {onscroll} bind:innerHeight bind:innerWidth />
- {#if isLoading}
- <div class="fixed start-0 end-0 bottom-0">
- <AppInfiniteProgress />
- </div>
- {/if}
- // Usage
- <script lang="ts">
- import AppInfiniteScroll from '$lib/components/layouts/AppInfiniteScroll.svelte';
- import type { OnChanged, PaginatedData, TranscriptResponse } from '$lib/types';
- import TranscriptListCard from './TranscriptListCard.svelte';
- let {
- groupedTranscripts,
- data,
- onloadmore
- }: {
- groupedTranscripts: Record<string, TranscriptResponse[]>;
- data: PaginatedData<TranscriptResponse>;
- onloadmore: OnChanged<number>;
- } = $props();
- </script>
- <div class="space-y-8">
- {#each Object.keys(groupedTranscripts) as key (key)}
- <div class="space-y-4">
- <h3 class="px-1 text-lg font-semibold text-gray-900">
- {key}
- </h3>
- <div class="space-y-3">
- {#each groupedTranscripts[key] as transcript (transcript.uuid)}
- <TranscriptListCard {transcript} />
- {/each}
- </div>
- </div>
- {/each}
- </div>
- <AppInfiniteScroll {data} {onloadmore} />
- // Paginated Data Class
- export class PaginatedData<T> {
- public readonly page: number;
- public readonly size?: number;
- public readonly pageCount: number;
- public readonly data: readonly T[];
- public readonly isLoading: boolean;
- public readonly pageError?: NetworkError;
- public readonly search?: PageSearch;
- private readonly _isPristine: boolean;
- // The primary constructor uses default parameter values.
- constructor({
- page = 1,
- size,
- pageCount = 1,
- data = [],
- isLoading = false,
- pageError,
- search,
- isPristine = false // Internal parameter to handle the pristine state
- }: {
- page?: number;
- size?: number;
- pageCount?: number;
- data?: readonly T[];
- isLoading?: boolean;
- pageError?: NetworkError;
- search?: PageSearch;
- isPristine?: boolean;
- }) {
- this.page = page;
- this.size = size;
- this.pageCount = pageCount;
- this.data = data;
- this.isLoading = isLoading;
- this.pageError = pageError;
- this.search = search;
- this._isPristine = isPristine;
- }
- public static pristine<T>({
- page = 1,
- size,
- pageCount = 1,
- data = [],
- isLoading = false,
- pageError
- }: {
- page?: number;
- size?: number;
- pageCount?: number;
- data?: readonly T[];
- isLoading?: boolean;
- pageError?: NetworkError;
- } = {}): PaginatedData<T> {
- return new PaginatedData<T>({
- page,
- size,
- pageCount,
- data,
- isLoading,
- pageError,
- search: undefined,
- isPristine: true
- });
- }
- public get isPristine(): boolean {
- return this._isPristine;
- }
- public get hasNext(): boolean {
- return this.page < this.pageCount;
- }
- public get hasError(): boolean {
- try {
- return Object.values(this.pageError as unknown as object).length > 0;
- } catch {
- return false;
- }
- }
- public get count(): number {
- return this.size ?? this.data.length;
- }
- public get hasData(): boolean {
- return this.data.length > 0;
- }
- public get next(): number {
- return this.page + 1;
- }
- public copyWith({
- page,
- pageCount,
- data,
- isLoading,
- pageError,
- size,
- search
- }: {
- page?: number;
- pageCount?: number;
- data?: readonly T[];
- isLoading?: boolean;
- pageError?: NetworkError | null;
- size?: number;
- search?: PageSearch | null;
- }): PaginatedData<T> {
- return new PaginatedData({
- page: page ?? this.page,
- pageCount: pageCount ?? this.pageCount,
- data: data ?? this.data,
- size: size ?? this.size,
- isLoading: isLoading ?? this.isLoading,
- pageError: pageError === null ? undefined : (pageError ?? this.pageError),
- search: search === null ? undefined : (search ?? this.search)
- });
- }
- public addData(
- pageData: PaginatedData<T>,
- { ensureUnique = false }: { ensureUnique?: boolean } = {}
- ): PaginatedData<T> {
- if (pageData.page <= 1) {
- return pageData;
- }
- if (this === pageData) {
- return this;
- }
- let items = [...this.data, ...pageData.data];
- if (ensureUnique) {
- items = Array.from(new Set(items));
- }
- return new PaginatedData({
- ...pageData,
- data: items
- });
- }
- public addSingleItem(item: T, { unshift = false }: { unshift?: boolean } = {}): PaginatedData<T> {
- const items = unshift ? [item, ...this.data] : [...this.data, item];
- return this.copyWith({
- data: items,
- size: (this.size ?? this.data.length) + 1
- });
- }
- public removeSingleItem(item: T): PaginatedData<T> {
- const items = this.data.filter((i) => i !== item);
- return this.copyWith({
- data: items,
- size: (this.size ?? this.data.length) - 1
- });
- }
- public compareTo(other: PaginatedData<T>): number {
- if (this.page < other.page) {
- return -1;
- }
- if (this.page > other.page) {
- return 1;
- }
- return 0;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment