Guest User

Untitled

a guest
Oct 5th, 2025
68
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // Infinite Scroll Observer Component
  2.  
  3. <script lang="ts">
  4.     import type { OnChanged, PaginatedData } from '$lib/types';
  5.     import AppInfiniteProgress from '../state/AppInfiniteProgress.svelte';
  6.  
  7.     type InfiniteScrollProps = {
  8.         threshold?: number;
  9.         data: PaginatedData<unknown>;
  10.         onloadmore: OnChanged<number>;
  11.     };
  12.     let { threshold = 40, data, onloadmore }: InfiniteScrollProps = $props();
  13.  
  14.     let isLoading = $derived(data.isLoading);
  15.     let page = $derived(data.page);
  16.     let pages = $derived(data.pageCount);
  17.     let scrollY = $state(0);
  18.     let innerHeight = $state(0);
  19.     let innerWidth = $state(0);
  20.     let lastScrollPosition = 0;
  21.  
  22.     function onscroll(e: Event) {
  23.         const documentHeight = Math.max(
  24.             document.body.scrollHeight,
  25.             document.body.offsetHeight,
  26.             document.documentElement.clientHeight,
  27.             document.documentElement.scrollHeight,
  28.             document.documentElement.offsetHeight
  29.         );
  30.         const maxScrollY = documentHeight - innerHeight;
  31.         const position = scrollY;
  32.         const offset = maxScrollY - scrollY;
  33.  
  34.         if (position < lastScrollPosition) {
  35.             lastScrollPosition = position;
  36.             return;
  37.         }
  38.         lastScrollPosition = position;
  39.  
  40.         if (!data.hasNext || offset > threshold || isLoading || page >= pages) return;
  41.  
  42.         onloadmore(page);
  43.     }
  44. </script>
  45.  
  46. <svelte:window bind:scrollY {onscroll} bind:innerHeight bind:innerWidth />
  47. {#if isLoading}
  48.     <div class="fixed start-0 end-0 bottom-0">
  49.         <AppInfiniteProgress />
  50.     </div>
  51. {/if}
  52.  
  53.  
  54. // Usage
  55. <script lang="ts">
  56.     import AppInfiniteScroll from '$lib/components/layouts/AppInfiniteScroll.svelte';
  57.     import type { OnChanged, PaginatedData, TranscriptResponse } from '$lib/types';
  58.     import TranscriptListCard from './TranscriptListCard.svelte';
  59.  
  60.     let {
  61.         groupedTranscripts,
  62.         data,
  63.         onloadmore
  64.     }: {
  65.         groupedTranscripts: Record<string, TranscriptResponse[]>;
  66.         data: PaginatedData<TranscriptResponse>;
  67.         onloadmore: OnChanged<number>;
  68.     } = $props();
  69. </script>
  70.  
  71. <div class="space-y-8">
  72.     {#each Object.keys(groupedTranscripts) as key (key)}
  73.         <div class="space-y-4">
  74.             <h3 class="px-1 text-lg font-semibold text-gray-900">
  75.                 {key}
  76.             </h3>
  77.  
  78.             <div class="space-y-3">
  79.                 {#each groupedTranscripts[key] as transcript (transcript.uuid)}
  80.                     <TranscriptListCard {transcript} />
  81.                 {/each}
  82.             </div>
  83.         </div>
  84.     {/each}
  85. </div>
  86. <AppInfiniteScroll {data} {onloadmore} />
  87.  
  88. // Paginated Data Class
  89.  
  90. export class PaginatedData<T> {
  91.     public readonly page: number;
  92.     public readonly size?: number;
  93.     public readonly pageCount: number;
  94.     public readonly data: readonly T[];
  95.     public readonly isLoading: boolean;
  96.     public readonly pageError?: NetworkError;
  97.     public readonly search?: PageSearch;
  98.     private readonly _isPristine: boolean;
  99.  
  100.     // The primary constructor uses default parameter values.
  101.     constructor({
  102.         page = 1,
  103.         size,
  104.         pageCount = 1,
  105.         data = [],
  106.         isLoading = false,
  107.         pageError,
  108.         search,
  109.         isPristine = false // Internal parameter to handle the pristine state
  110.     }: {
  111.         page?: number;
  112.         size?: number;
  113.         pageCount?: number;
  114.         data?: readonly T[];
  115.         isLoading?: boolean;
  116.         pageError?: NetworkError;
  117.         search?: PageSearch;
  118.         isPristine?: boolean;
  119.     }) {
  120.         this.page = page;
  121.         this.size = size;
  122.         this.pageCount = pageCount;
  123.         this.data = data;
  124.         this.isLoading = isLoading;
  125.         this.pageError = pageError;
  126.         this.search = search;
  127.         this._isPristine = isPristine;
  128.     }
  129.  
  130.     public static pristine<T>({
  131.         page = 1,
  132.         size,
  133.         pageCount = 1,
  134.         data = [],
  135.         isLoading = false,
  136.         pageError
  137.     }: {
  138.         page?: number;
  139.         size?: number;
  140.         pageCount?: number;
  141.         data?: readonly T[];
  142.         isLoading?: boolean;
  143.         pageError?: NetworkError;
  144.     } = {}): PaginatedData<T> {
  145.         return new PaginatedData<T>({
  146.             page,
  147.             size,
  148.             pageCount,
  149.             data,
  150.             isLoading,
  151.             pageError,
  152.             search: undefined,
  153.             isPristine: true
  154.         });
  155.     }
  156.  
  157.     public get isPristine(): boolean {
  158.         return this._isPristine;
  159.     }
  160.  
  161.     public get hasNext(): boolean {
  162.         return this.page < this.pageCount;
  163.     }
  164.  
  165.     public get hasError(): boolean {
  166.         try {
  167.             return Object.values(this.pageError as unknown as object).length > 0;
  168.         } catch {
  169.             return false;
  170.         }
  171.     }
  172.  
  173.     public get count(): number {
  174.         return this.size ?? this.data.length;
  175.     }
  176.  
  177.     public get hasData(): boolean {
  178.         return this.data.length > 0;
  179.     }
  180.  
  181.     public get next(): number {
  182.         return this.page + 1;
  183.     }
  184.  
  185.     public copyWith({
  186.         page,
  187.         pageCount,
  188.         data,
  189.         isLoading,
  190.         pageError,
  191.         size,
  192.         search
  193.     }: {
  194.         page?: number;
  195.         pageCount?: number;
  196.         data?: readonly T[];
  197.         isLoading?: boolean;
  198.         pageError?: NetworkError | null;
  199.         size?: number;
  200.         search?: PageSearch | null;
  201.     }): PaginatedData<T> {
  202.         return new PaginatedData({
  203.             page: page ?? this.page,
  204.             pageCount: pageCount ?? this.pageCount,
  205.             data: data ?? this.data,
  206.             size: size ?? this.size,
  207.             isLoading: isLoading ?? this.isLoading,
  208.             pageError: pageError === null ? undefined : (pageError ?? this.pageError),
  209.             search: search === null ? undefined : (search ?? this.search)
  210.         });
  211.     }
  212.  
  213.     public addData(
  214.         pageData: PaginatedData<T>,
  215.         { ensureUnique = false }: { ensureUnique?: boolean } = {}
  216.     ): PaginatedData<T> {
  217.         if (pageData.page <= 1) {
  218.             return pageData;
  219.         }
  220.  
  221.         if (this === pageData) {
  222.             return this;
  223.         }
  224.  
  225.         let items = [...this.data, ...pageData.data];
  226.  
  227.         if (ensureUnique) {
  228.             items = Array.from(new Set(items));
  229.         }
  230.  
  231.         return new PaginatedData({
  232.             ...pageData,
  233.             data: items
  234.         });
  235.     }
  236.  
  237.     public addSingleItem(item: T, { unshift = false }: { unshift?: boolean } = {}): PaginatedData<T> {
  238.         const items = unshift ? [item, ...this.data] : [...this.data, item];
  239.         return this.copyWith({
  240.             data: items,
  241.             size: (this.size ?? this.data.length) + 1
  242.         });
  243.     }
  244.  
  245.     public removeSingleItem(item: T): PaginatedData<T> {
  246.         const items = this.data.filter((i) => i !== item);
  247.         return this.copyWith({
  248.             data: items,
  249.             size: (this.size ?? this.data.length) - 1
  250.         });
  251.     }
  252.  
  253.     public compareTo(other: PaginatedData<T>): number {
  254.         if (this.page < other.page) {
  255.             return -1;
  256.         }
  257.         if (this.page > other.page) {
  258.             return 1;
  259.         }
  260.         return 0;
  261.     }
  262. }
  263.  
Advertisement
Add Comment
Please, Sign In to add comment