port profile and explore pages to useInfiniteLoader
This commit is contained in:
parent
1933391d38
commit
133cd9ec5b
3 changed files with 57 additions and 66 deletions
|
|
@ -1,11 +1,12 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
import { onDestroy } from 'svelte'
|
||||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
import ExploreGrid from '$lib/components/explore/ExploreGrid.svelte'
|
import ExploreGrid from '$lib/components/explore/ExploreGrid.svelte'
|
||||||
import ProfileHeader from '$lib/components/profile/ProfileHeader.svelte'
|
import ProfileHeader from '$lib/components/profile/ProfileHeader.svelte'
|
||||||
import { userQueries } from '$lib/api/queries/user.queries'
|
import { userQueries } from '$lib/api/queries/user.queries'
|
||||||
import { crewStore } from '$lib/stores/crew.store.svelte'
|
import { crewStore } from '$lib/stores/crew.store.svelte'
|
||||||
import { IsInViewport } from 'runed'
|
import { useInfiniteLoader } from '$lib/stores/loaderState.svelte'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
import Button from '$lib/components/ui/Button.svelte'
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
import PageMeta from '$lib/components/PageMeta.svelte'
|
import PageMeta from '$lib/components/PageMeta.svelte'
|
||||||
|
|
@ -18,6 +19,8 @@
|
||||||
const viewerCrewRole = $derived(crewStore.membership?.role ?? null)
|
const viewerCrewRole = $derived(crewStore.membership?.role ?? null)
|
||||||
const viewerCrewId = $derived(crewStore.crew?.id ?? null)
|
const viewerCrewId = $derived(crewStore.crew?.id ?? null)
|
||||||
|
|
||||||
|
let sentinelEl = $state<HTMLElement>()
|
||||||
|
|
||||||
const partiesQuery = createInfiniteQuery(() => ({
|
const partiesQuery = createInfiniteQuery(() => ({
|
||||||
...userQueries.parties(data.user?.username ?? ''),
|
...userQueries.parties(data.user?.username ?? ''),
|
||||||
enabled: !!data.user?.username,
|
enabled: !!data.user?.username,
|
||||||
|
|
@ -38,30 +41,18 @@
|
||||||
initialDataUpdatedAt: 0
|
initialDataUpdatedAt: 0
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// State-gated infinite scroll
|
||||||
|
const loader = useInfiniteLoader(() => partiesQuery, () => sentinelEl, { rootMargin: '300px' })
|
||||||
|
|
||||||
|
// Cleanup on destroy
|
||||||
|
onDestroy(() => loader.destroy())
|
||||||
|
|
||||||
const items = $derived(() => {
|
const items = $derived(() => {
|
||||||
if (!partiesQuery.data?.pages) return data.items || []
|
if (!partiesQuery.data?.pages) return data.items || []
|
||||||
return partiesQuery.data.pages.flatMap((page) => page.results ?? [])
|
return partiesQuery.data.pages.flatMap((page) => page.results ?? [])
|
||||||
})
|
})
|
||||||
|
|
||||||
const isEmpty = $derived(!partiesQuery.isLoading && items().length === 0)
|
const isEmpty = $derived(!partiesQuery.isLoading && items().length === 0)
|
||||||
const showSentinel = $derived(partiesQuery.hasNextPage && !partiesQuery.isFetchingNextPage)
|
|
||||||
|
|
||||||
let sentinelEl = $state<HTMLElement>()
|
|
||||||
|
|
||||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
|
||||||
rootMargin: '300px'
|
|
||||||
})
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (
|
|
||||||
inViewport.current &&
|
|
||||||
partiesQuery.hasNextPage &&
|
|
||||||
!partiesQuery.isFetchingNextPage &&
|
|
||||||
!partiesQuery.isLoading
|
|
||||||
) {
|
|
||||||
partiesQuery.fetchNextPage()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PageMeta
|
<PageMeta
|
||||||
|
|
@ -104,9 +95,11 @@
|
||||||
<div class="profile-grid">
|
<div class="profile-grid">
|
||||||
<ExploreGrid items={items()} />
|
<ExploreGrid items={items()} />
|
||||||
|
|
||||||
{#if showSentinel}
|
<div
|
||||||
<div class="load-more-sentinel" bind:this={sentinelEl}></div>
|
class="load-more-sentinel"
|
||||||
{/if}
|
bind:this={sentinelEl}
|
||||||
|
class:hidden={!partiesQuery.hasNextPage}
|
||||||
|
></div>
|
||||||
|
|
||||||
{#if partiesQuery.isFetchingNextPage}
|
{#if partiesQuery.isFetchingNextPage}
|
||||||
<div class="loading-more">
|
<div class="loading-more">
|
||||||
|
|
@ -169,6 +162,10 @@
|
||||||
.load-more-sentinel {
|
.load-more-sentinel {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin-top: $unit;
|
margin-top: $unit;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-more {
|
.loading-more {
|
||||||
|
|
|
||||||
|
|
@ -2,42 +2,33 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
import { onDestroy } from 'svelte'
|
||||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
import ExploreGrid from '$lib/components/explore/ExploreGrid.svelte'
|
import ExploreGrid from '$lib/components/explore/ExploreGrid.svelte'
|
||||||
import ProfileHeader from '$lib/components/profile/ProfileHeader.svelte'
|
import ProfileHeader from '$lib/components/profile/ProfileHeader.svelte'
|
||||||
import { userQueries } from '$lib/api/queries/user.queries'
|
import { userQueries } from '$lib/api/queries/user.queries'
|
||||||
import { IsInViewport } from 'runed'
|
import { useInfiniteLoader } from '$lib/stores/loaderState.svelte'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
import Button from '$lib/components/ui/Button.svelte'
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
|
|
||||||
const { data }: { data: PageData } = $props()
|
const { data }: { data: PageData } = $props()
|
||||||
|
|
||||||
|
let sentinelEl = $state<HTMLElement>()
|
||||||
|
|
||||||
const favoritesQuery = createInfiniteQuery(() => userQueries.favorites())
|
const favoritesQuery = createInfiniteQuery(() => userQueries.favorites())
|
||||||
|
|
||||||
|
// State-gated infinite scroll
|
||||||
|
const loader = useInfiniteLoader(() => favoritesQuery, () => sentinelEl, { rootMargin: '300px' })
|
||||||
|
|
||||||
|
// Cleanup on destroy
|
||||||
|
onDestroy(() => loader.destroy())
|
||||||
|
|
||||||
const items = $derived(() => {
|
const items = $derived(() => {
|
||||||
if (!favoritesQuery.data?.pages) return []
|
if (!favoritesQuery.data?.pages) return []
|
||||||
return favoritesQuery.data.pages.flatMap((page) => page.items ?? [])
|
return favoritesQuery.data.pages.flatMap((page) => page.items ?? [])
|
||||||
})
|
})
|
||||||
|
|
||||||
const isEmpty = $derived(!favoritesQuery.isLoading && items().length === 0)
|
const isEmpty = $derived(!favoritesQuery.isLoading && items().length === 0)
|
||||||
const showSentinel = $derived(favoritesQuery.hasNextPage && !favoritesQuery.isFetchingNextPage)
|
|
||||||
|
|
||||||
let sentinelEl = $state<HTMLElement>()
|
|
||||||
|
|
||||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
|
||||||
rootMargin: '300px'
|
|
||||||
})
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (
|
|
||||||
inViewport.current &&
|
|
||||||
favoritesQuery.hasNextPage &&
|
|
||||||
!favoritesQuery.isFetchingNextPage &&
|
|
||||||
!favoritesQuery.isLoading
|
|
||||||
) {
|
|
||||||
favoritesQuery.fetchNextPage()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
|
@ -77,9 +68,11 @@
|
||||||
<div class="profile-grid">
|
<div class="profile-grid">
|
||||||
<ExploreGrid items={items()} />
|
<ExploreGrid items={items()} />
|
||||||
|
|
||||||
{#if showSentinel}
|
<div
|
||||||
<div class="load-more-sentinel" bind:this={sentinelEl}></div>
|
class="load-more-sentinel"
|
||||||
{/if}
|
bind:this={sentinelEl}
|
||||||
|
class:hidden={!favoritesQuery.hasNextPage}
|
||||||
|
></div>
|
||||||
|
|
||||||
{#if favoritesQuery.isFetchingNextPage}
|
{#if favoritesQuery.isFetchingNextPage}
|
||||||
<div class="loading-more">
|
<div class="loading-more">
|
||||||
|
|
@ -142,6 +135,10 @@
|
||||||
.load-more-sentinel {
|
.load-more-sentinel {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin-top: $unit;
|
margin-top: $unit;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-more {
|
.loading-more {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types'
|
import type { PageData } from './$types'
|
||||||
|
import { onDestroy } from 'svelte'
|
||||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||||
import ExploreGrid from '$lib/components/explore/ExploreGrid.svelte'
|
import ExploreGrid from '$lib/components/explore/ExploreGrid.svelte'
|
||||||
import { partyQueries } from '$lib/api/queries/party.queries'
|
import { partyQueries } from '$lib/api/queries/party.queries'
|
||||||
import { IsInViewport } from 'runed'
|
import { useInfiniteLoader } from '$lib/stores/loaderState.svelte'
|
||||||
import Icon from '$lib/components/Icon.svelte'
|
import Icon from '$lib/components/Icon.svelte'
|
||||||
import Button from '$lib/components/ui/Button.svelte'
|
import Button from '$lib/components/ui/Button.svelte'
|
||||||
import PageMeta from '$lib/components/PageMeta.svelte'
|
import PageMeta from '$lib/components/PageMeta.svelte'
|
||||||
|
|
@ -11,6 +12,8 @@
|
||||||
|
|
||||||
const { data } = $props() as { data: PageData }
|
const { data } = $props() as { data: PageData }
|
||||||
|
|
||||||
|
let sentinelEl = $state<HTMLElement>()
|
||||||
|
|
||||||
const partiesQuery = createInfiniteQuery(() => ({
|
const partiesQuery = createInfiniteQuery(() => ({
|
||||||
...partyQueries.list(),
|
...partyQueries.list(),
|
||||||
initialData: data.items
|
initialData: data.items
|
||||||
|
|
@ -30,29 +33,17 @@
|
||||||
initialDataUpdatedAt: 0
|
initialDataUpdatedAt: 0
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// State-gated infinite scroll
|
||||||
|
const loader = useInfiniteLoader(() => partiesQuery, () => sentinelEl, { rootMargin: '300px' })
|
||||||
|
|
||||||
|
// Cleanup on destroy
|
||||||
|
onDestroy(() => loader.destroy())
|
||||||
|
|
||||||
const items = $derived(
|
const items = $derived(
|
||||||
partiesQuery.data?.pages.flatMap((page) => page.results) ?? data.items ?? []
|
partiesQuery.data?.pages.flatMap((page) => page.results) ?? data.items ?? []
|
||||||
)
|
)
|
||||||
|
|
||||||
const isEmpty = $derived(!partiesQuery.isLoading && items.length === 0)
|
const isEmpty = $derived(!partiesQuery.isLoading && items.length === 0)
|
||||||
const showSentinel = $derived(partiesQuery.hasNextPage && !partiesQuery.isFetchingNextPage)
|
|
||||||
|
|
||||||
let sentinelEl = $state<HTMLElement>()
|
|
||||||
|
|
||||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
|
||||||
rootMargin: '300px'
|
|
||||||
})
|
|
||||||
|
|
||||||
$effect(() => {
|
|
||||||
if (
|
|
||||||
inViewport.current &&
|
|
||||||
partiesQuery.hasNextPage &&
|
|
||||||
!partiesQuery.isFetchingNextPage &&
|
|
||||||
!partiesQuery.isLoading
|
|
||||||
) {
|
|
||||||
partiesQuery.fetchNextPage()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<PageMeta title={m.page_title_teams()} description={m.page_desc_teams()} />
|
<PageMeta title={m.page_title_teams()} description={m.page_desc_teams()} />
|
||||||
|
|
@ -81,9 +72,11 @@
|
||||||
<div class="explore-grid">
|
<div class="explore-grid">
|
||||||
<ExploreGrid items={items} />
|
<ExploreGrid items={items} />
|
||||||
|
|
||||||
{#if showSentinel}
|
<div
|
||||||
<div class="load-more-sentinel" bind:this={sentinelEl}></div>
|
class="load-more-sentinel"
|
||||||
{/if}
|
bind:this={sentinelEl}
|
||||||
|
class:hidden={!partiesQuery.hasNextPage}
|
||||||
|
></div>
|
||||||
|
|
||||||
{#if partiesQuery.isFetchingNextPage}
|
{#if partiesQuery.isFetchingNextPage}
|
||||||
<div class="loading-more">
|
<div class="loading-more">
|
||||||
|
|
@ -150,6 +143,10 @@
|
||||||
.load-more-sentinel {
|
.load-more-sentinel {
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin-top: $unit;
|
margin-top: $unit;
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.loading-more {
|
.loading-more {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue