port collection pages to useInfiniteLoader
This commit is contained in:
parent
26299d5d10
commit
1933391d38
4 changed files with 68 additions and 70 deletions
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from './$types'
|
||||
import type { CollectionArtifact } from '$lib/types/api/artifact'
|
||||
import { getContext } from 'svelte'
|
||||
import { getContext, onDestroy } from 'svelte'
|
||||
import { createInfiniteQuery, createQuery } from '@tanstack/svelte-query'
|
||||
import { artifactQueries } from '$lib/api/queries/artifact.queries'
|
||||
import CollectionArtifactDetailPane from '$lib/components/collection/CollectionArtifactDetailPane.svelte'
|
||||
|
|
@ -12,12 +12,12 @@
|
|||
import Icon from '$lib/components/Icon.svelte'
|
||||
import ViewModeToggle from '$lib/components/ui/ViewModeToggle.svelte'
|
||||
import MultiSelect from '$lib/components/ui/MultiSelect.svelte'
|
||||
import { IsInViewport } from 'runed'
|
||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
||||
import Select from '$lib/components/ui/Select.svelte'
|
||||
import { getArtifactImage } from '$lib/utils/images'
|
||||
import { LOADED_IDS_KEY, type LoadedIdsContext } from '$lib/stores/selectionMode.svelte'
|
||||
import { useInfiniteLoader } from '$lib/stores/loaderState.svelte'
|
||||
|
||||
const { data }: { data: PageData } = $props()
|
||||
|
||||
|
|
@ -130,6 +130,10 @@
|
|||
return artifactQueries.collection(userId, filters)
|
||||
})
|
||||
|
||||
// State-gated infinite scroll (inspired by svelte-infinite)
|
||||
// Encapsulates intersection observer, state machine, and all reactive effects
|
||||
const loader = useInfiniteLoader(() => collectionQuery, () => sentinelEl)
|
||||
|
||||
// Flatten all artifacts from pages
|
||||
const allArtifacts = $derived.by((): CollectionArtifact[] => {
|
||||
if (!collectionQuery.data?.pages) {
|
||||
|
|
@ -144,25 +148,17 @@
|
|||
loadedIdsContext?.setIds(ids)
|
||||
})
|
||||
|
||||
// Infinite scroll
|
||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||
rootMargin: '200px'
|
||||
// Reset loader state when filters change
|
||||
$effect(() => {
|
||||
void queryFilters
|
||||
loader.reset()
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (
|
||||
inViewport.current &&
|
||||
collectionQuery.hasNextPage &&
|
||||
!collectionQuery.isFetchingNextPage &&
|
||||
!collectionQuery.isLoading
|
||||
) {
|
||||
collectionQuery.fetchNextPage()
|
||||
}
|
||||
})
|
||||
// Cleanup on destroy
|
||||
onDestroy(() => loader.destroy())
|
||||
|
||||
const isLoading = $derived(collectionQuery.isLoading)
|
||||
const isEmpty = $derived(!isLoading && allArtifacts.length === 0)
|
||||
const showSentinel = $derived(collectionQuery.hasNextPage && !collectionQuery.isFetchingNextPage)
|
||||
|
||||
// Current view mode from store
|
||||
const currentViewMode = $derived(viewMode.collectionView)
|
||||
|
|
@ -291,9 +287,12 @@
|
|||
{/if}
|
||||
|
||||
{#if !isLoading && !isEmpty}
|
||||
{#if showSentinel}
|
||||
<div class="load-more-sentinel" bind:this={sentinelEl}></div>
|
||||
{/if}
|
||||
<!-- Sentinel always in DOM to avoid Svelte block tracking issues during rapid updates -->
|
||||
<div
|
||||
class="load-more-sentinel"
|
||||
bind:this={sentinelEl}
|
||||
class:hidden={!collectionQuery.hasNextPage}
|
||||
></div>
|
||||
|
||||
{#if collectionQuery.isFetchingNextPage}
|
||||
<div class="loading-more">
|
||||
|
|
@ -301,7 +300,6 @@
|
|||
<span>Loading more...</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -397,6 +395,10 @@
|
|||
.load-more-sentinel {
|
||||
height: 1px;
|
||||
margin-top: $unit;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from './$types'
|
||||
import type { CollectionCharacter, CollectionSortKey } from '$lib/types/api/collection'
|
||||
import { getContext } from 'svelte'
|
||||
import { getContext, onDestroy } from 'svelte'
|
||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
||||
import CollectionFilters, {
|
||||
|
|
@ -13,10 +13,10 @@
|
|||
import SelectableCollectionCard from '$lib/components/collection/SelectableCollectionCard.svelte'
|
||||
import SelectableCollectionRow from '$lib/components/collection/SelectableCollectionRow.svelte'
|
||||
import Icon from '$lib/components/Icon.svelte'
|
||||
import { IsInViewport } from 'runed'
|
||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
||||
import { LOADED_IDS_KEY, type LoadedIdsContext } from '$lib/stores/selectionMode.svelte'
|
||||
import { useInfiniteLoader } from '$lib/stores/loaderState.svelte'
|
||||
|
||||
const { data }: { data: PageData } = $props()
|
||||
|
||||
|
|
@ -58,6 +58,10 @@
|
|||
return collectionQueries.characters(userId, filters)
|
||||
})
|
||||
|
||||
// State-gated infinite scroll (inspired by svelte-infinite)
|
||||
// Encapsulates intersection observer, state machine, and all reactive effects
|
||||
const loader = useInfiniteLoader(() => collectionQuery, () => sentinelEl)
|
||||
|
||||
// Flatten all characters from pages
|
||||
const allCharacters = $derived.by((): CollectionCharacter[] => {
|
||||
if (!collectionQuery.data?.pages) {
|
||||
|
|
@ -72,21 +76,14 @@
|
|||
loadedIdsContext?.setIds(ids)
|
||||
})
|
||||
|
||||
// Infinite scroll
|
||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||
rootMargin: '200px'
|
||||
// Reset loader state when filters change
|
||||
$effect(() => {
|
||||
void queryFilters
|
||||
loader.reset()
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (
|
||||
inViewport.current &&
|
||||
collectionQuery.hasNextPage &&
|
||||
!collectionQuery.isFetchingNextPage &&
|
||||
!collectionQuery.isLoading
|
||||
) {
|
||||
collectionQuery.fetchNextPage()
|
||||
}
|
||||
})
|
||||
// Cleanup on destroy
|
||||
onDestroy(() => loader.destroy())
|
||||
|
||||
const isLoading = $derived(collectionQuery.isLoading)
|
||||
const isEmpty = $derived(!isLoading && allCharacters.length === 0)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from './$types'
|
||||
import type { CollectionSummon, CollectionSortKey } from '$lib/types/api/collection'
|
||||
import { getContext } from 'svelte'
|
||||
import { getContext, onDestroy } from 'svelte'
|
||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
||||
import CollectionFilters, {
|
||||
|
|
@ -13,10 +13,10 @@
|
|||
import SelectableCollectionCard from '$lib/components/collection/SelectableCollectionCard.svelte'
|
||||
import SelectableCollectionRow from '$lib/components/collection/SelectableCollectionRow.svelte'
|
||||
import Icon from '$lib/components/Icon.svelte'
|
||||
import { IsInViewport } from 'runed'
|
||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
||||
import { LOADED_IDS_KEY, type LoadedIdsContext } from '$lib/stores/selectionMode.svelte'
|
||||
import { useInfiniteLoader } from '$lib/stores/loaderState.svelte'
|
||||
|
||||
const { data }: { data: PageData } = $props()
|
||||
|
||||
|
|
@ -52,6 +52,10 @@
|
|||
return collectionQueries.summons(userId, filters)
|
||||
})
|
||||
|
||||
// State-gated infinite scroll (inspired by svelte-infinite)
|
||||
// Encapsulates intersection observer, state machine, and all reactive effects
|
||||
const loader = useInfiniteLoader(() => collectionQuery, () => sentinelEl)
|
||||
|
||||
// Flatten all summons from pages
|
||||
const allSummons = $derived.by((): CollectionSummon[] => {
|
||||
if (!collectionQuery.data?.pages) {
|
||||
|
|
@ -66,25 +70,17 @@
|
|||
loadedIdsContext?.setIds(ids)
|
||||
})
|
||||
|
||||
// Infinite scroll
|
||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||
rootMargin: '200px'
|
||||
// Reset loader state when filters change
|
||||
$effect(() => {
|
||||
void queryFilters
|
||||
loader.reset()
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (
|
||||
inViewport.current &&
|
||||
collectionQuery.hasNextPage &&
|
||||
!collectionQuery.isFetchingNextPage &&
|
||||
!collectionQuery.isLoading
|
||||
) {
|
||||
collectionQuery.fetchNextPage()
|
||||
}
|
||||
})
|
||||
// Cleanup on destroy
|
||||
onDestroy(() => loader.destroy())
|
||||
|
||||
const isLoading = $derived(collectionQuery.isLoading)
|
||||
const isEmpty = $derived(!isLoading && allSummons.length === 0)
|
||||
const showSentinel = $derived(collectionQuery.hasNextPage && !collectionQuery.isFetchingNextPage)
|
||||
|
||||
// Current view mode from store
|
||||
const currentViewMode = $derived(viewMode.collectionView)
|
||||
|
|
@ -165,9 +161,12 @@
|
|||
{/if}
|
||||
|
||||
{#if !isLoading && !isEmpty}
|
||||
{#if showSentinel}
|
||||
<div class="load-more-sentinel" bind:this={sentinelEl}></div>
|
||||
{/if}
|
||||
<!-- Sentinel always in DOM to avoid Svelte block tracking issues during rapid updates -->
|
||||
<div
|
||||
class="load-more-sentinel"
|
||||
bind:this={sentinelEl}
|
||||
class:hidden={!collectionQuery.hasNextPage}
|
||||
></div>
|
||||
|
||||
{#if collectionQuery.isFetchingNextPage}
|
||||
<div class="loading-more">
|
||||
|
|
@ -175,7 +174,6 @@
|
|||
<span>Loading more...</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -247,6 +245,10 @@
|
|||
.load-more-sentinel {
|
||||
height: 1px;
|
||||
margin-top: $unit;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-more {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import type { PageData } from './$types'
|
||||
import type { CollectionWeapon, CollectionSortKey } from '$lib/types/api/collection'
|
||||
import { getContext } from 'svelte'
|
||||
import { getContext, onDestroy } from 'svelte'
|
||||
import { createInfiniteQuery } from '@tanstack/svelte-query'
|
||||
import { collectionQueries } from '$lib/api/queries/collection.queries'
|
||||
import CollectionFilters, {
|
||||
|
|
@ -13,10 +13,10 @@
|
|||
import SelectableCollectionCard from '$lib/components/collection/SelectableCollectionCard.svelte'
|
||||
import SelectableCollectionRow from '$lib/components/collection/SelectableCollectionRow.svelte'
|
||||
import Icon from '$lib/components/Icon.svelte'
|
||||
import { IsInViewport } from 'runed'
|
||||
import { sidebar } from '$lib/stores/sidebar.svelte'
|
||||
import { viewMode, type ViewMode } from '$lib/stores/viewMode.svelte'
|
||||
import { LOADED_IDS_KEY, type LoadedIdsContext } from '$lib/stores/selectionMode.svelte'
|
||||
import { useInfiniteLoader } from '$lib/stores/loaderState.svelte'
|
||||
|
||||
const { data }: { data: PageData } = $props()
|
||||
|
||||
|
|
@ -56,6 +56,10 @@
|
|||
return collectionQueries.weapons(userId, filters)
|
||||
})
|
||||
|
||||
// State-gated infinite scroll (inspired by svelte-infinite)
|
||||
// Encapsulates intersection observer, state machine, and all reactive effects
|
||||
const loader = useInfiniteLoader(() => collectionQuery, () => sentinelEl)
|
||||
|
||||
// Flatten all weapons from pages
|
||||
const allWeapons = $derived.by((): CollectionWeapon[] => {
|
||||
if (!collectionQuery.data?.pages) {
|
||||
|
|
@ -70,21 +74,14 @@
|
|||
loadedIdsContext?.setIds(ids)
|
||||
})
|
||||
|
||||
// Infinite scroll
|
||||
const inViewport = new IsInViewport(() => sentinelEl, {
|
||||
rootMargin: '200px'
|
||||
// Reset loader state when filters change
|
||||
$effect(() => {
|
||||
void queryFilters
|
||||
loader.reset()
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (
|
||||
inViewport.current &&
|
||||
collectionQuery.hasNextPage &&
|
||||
!collectionQuery.isFetchingNextPage &&
|
||||
!collectionQuery.isLoading
|
||||
) {
|
||||
collectionQuery.fetchNextPage()
|
||||
}
|
||||
})
|
||||
// Cleanup on destroy
|
||||
onDestroy(() => loader.destroy())
|
||||
|
||||
const isLoading = $derived(collectionQuery.isLoading)
|
||||
const isEmpty = $derived(!isLoading && allWeapons.length === 0)
|
||||
|
|
|
|||
Loading…
Reference in a new issue