Skip to content

Commit

Permalink
Implement infinite scroll for list items
Browse files Browse the repository at this point in the history
  • Loading branch information
kyoshino committed Dec 17, 2024
1 parent c2e4105 commit 93daf21
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 36 deletions.
19 changes: 10 additions & 9 deletions src/lib/components/assets/list/asset-list.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<script>
import { GridBody } from '@sveltia/ui';
import { sleep } from '@sveltia/utils/misc';
import { _ } from 'svelte-i18n';
import AssetListItem from '$lib/components/assets/list/asset-list-item.svelte';
import DropZone from '$lib/components/assets/shared/drop-zone.svelte';
import EmptyState from '$lib/components/common/empty-state.svelte';
import InfiniteScroll from '$lib/components/common/infinite-scroll.svelte';
import ListContainer from '$lib/components/common/list-container.svelte';
import ListingGrid from '$lib/components/common/listing-grid.svelte';
import { globalAssetFolder, selectedAssetFolder, uploadingAssets } from '$lib/services/assets';
import { assetGroups, currentView, listedAssets } from '$lib/services/assets/view';
$: viewType = $currentView.type;
// Can’t upload assets if collection assets are saved at entry-relative paths
$: uploadDisabled = !!$selectedAssetFolder?.entryRelative;
</script>
Expand All @@ -28,19 +29,19 @@
{#if Object.values($assetGroups).flat(1).length}
<ListingGrid
id="asset-list"
viewType={$currentView.type}
{viewType}
aria-label={$_('assets')}
aria-rowcount={$listedAssets.length}
>
{#each Object.entries($assetGroups) as [name, assets] (name)}
<GridBody label={name !== '*' ? name : undefined}>
{#each assets as asset (asset.path)}
{#key asset.sha}
{#await sleep(0) then}
<AssetListItem {asset} viewType={$currentView.type} />
{/await}
{/key}
{/each}
<InfiniteScroll items={assets} itemKey="path">
{#snippet renderItem(/** @type {Asset} */ asset)}
{#key asset.sha}
<AssetListItem {asset} {viewType} />
{/key}
{/snippet}
</InfiniteScroll>
</GridBody>
{/each}
</ListingGrid>
Expand Down
65 changes: 65 additions & 0 deletions src/lib/components/common/infinite-scroll.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<!--
@component Enable infinite scroll for list items for better rendering performance.
@see https://svelte.dev/docs/svelte/v5-migration-guide#Snippets-instead-of-slots-Passing-data-back-up
-->
<script>
/**
* @typedef {object} Props
* @property {any[]} items - Item list.
* @property {string} itemKey - Item key used for the `each` loop.
* @property {number} [itemChunkSize] - Number of items to be loaded at once.
* @property {import('svelte').Snippet<[any]>} renderItem - Snippet to render each item.
*/
/** @type {Props} */
let {
/* eslint-disable prefer-const */
items,
itemKey,
itemChunkSize = 25,
renderItem,
/* eslint-enable prefer-const */
} = $props();
/**
* @type {number}
*/
let loadedItemSize = $state(itemChunkSize);
/**
* @type {HTMLElement | undefined}
*/
let spinner = $state(undefined);
const loading = $derived(items.length > loadedItemSize);
const observer = new IntersectionObserver(([{ isIntersecting }]) => {
if (isIntersecting) {
if (loading) {
loadedItemSize += itemChunkSize;
} else {
observer.disconnect();
}
}
});
$effect(() => {
if (spinner) {
observer.observe(spinner);
}
});
</script>

{#each items.slice(0, loadedItemSize) as item (item[itemKey])}
{@render renderItem(item)}
{/each}

{#if loading}
<div role="none" class="spinner" bind:this={spinner}></div>
{/if}

<style>
.spinner {
height: 1px;
}
</style>
9 changes: 6 additions & 3 deletions src/lib/components/common/listing-grid.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
-->
<script>
import { Grid } from '@sveltia/ui';
import { sleep } from '@sveltia/utils/misc';
/**
* View type.
Expand All @@ -13,9 +14,11 @@
</script>

<div role="none" class="{viewType}-view">
<Grid multiple clickToSelect={false} {...$$restProps}>
<slot />
</Grid>
{#await sleep(0) then}
<Grid multiple clickToSelect={false} {...$$restProps}>
<slot />
</Grid>
{/await}
</div>

<style lang="scss">
Expand Down
21 changes: 11 additions & 10 deletions src/lib/components/contents/list/entry-list.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script>
import { Button, GridBody, Icon } from '@sveltia/ui';
import { sleep } from '@sveltia/utils/misc';
import { _ } from 'svelte-i18n';
import EmptyState from '$lib/components/common/empty-state.svelte';
import InfiniteScroll from '$lib/components/common/infinite-scroll.svelte';
import ListContainer from '$lib/components/common/list-container.svelte';
import ListingGrid from '$lib/components/common/listing-grid.svelte';
import EntryListItem from '$lib/components/contents/list/entry-list-item.svelte';
Expand All @@ -28,15 +28,16 @@
{#each $entryGroups as { name, entries } (name)}
<!-- @todo Implement custom table column option that can replace summary template -->
<GridBody label={name !== '*' ? name : undefined}>
{#each entries as entry (entry.id)}
{#await sleep(0) then}
{@const { locales } = entry}
{@const { content } = locales[defaultLocale] ?? Object.values(locales)[0] ?? {}}
{#if content}
<EntryListItem {collection} {entry} {viewType} />
{/if}
{/await}
{/each}
<InfiniteScroll
items={entries.filter(
({ locales }) => !!(locales[defaultLocale] ?? Object.values(locales)[0])?.content,
)}
itemKey="id"
>
{#snippet renderItem(/** @type {Entry} */ entry)}
<EntryListItem {collection} {entry} {viewType} />
{/snippet}
</InfiniteScroll>
</GridBody>
{/each}
</ListingGrid>
Expand Down
10 changes: 5 additions & 5 deletions src/lib/components/contents/list/file-list.svelte
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<script>
import { GridCell, GridRow } from '@sveltia/ui';
import { sleep } from '@sveltia/utils/misc';
import { _ } from 'svelte-i18n';
import EmptyState from '$lib/components/common/empty-state.svelte';
import InfiniteScroll from '$lib/components/common/infinite-scroll.svelte';
import ListContainer from '$lib/components/common/list-container.svelte';
import ListingGrid from '$lib/components/common/listing-grid.svelte';
import { goto } from '$lib/services/app/navigation';
Expand All @@ -16,8 +16,8 @@
aria-label={$_('files')}
aria-rowcount={$selectedCollection.files.length}
>
{#each $selectedCollection.files as { name, label } (name)}
{#await sleep(0) then}
<InfiniteScroll items={$selectedCollection.files} itemKey="name">
{#snippet renderItem(/** @type {RawCollectionFile} */ { name, label })}
<GridRow
onclick={() => {
goto(`/collections/${$selectedCollection?.name}/entries/${name}`);
Expand All @@ -27,8 +27,8 @@
{label || name}
</GridCell>
</GridRow>
{/await}
{/each}
{/snippet}
</InfiniteScroll>
</ListingGrid>
{:else}
<EmptyState>
Expand Down
18 changes: 9 additions & 9 deletions src/lib/components/search/search-results.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script>
import { Group } from '@sveltia/ui';
import { sleep } from '@sveltia/utils/misc';
import { _ } from 'svelte-i18n';
import InfiniteScroll from '$lib/components/common/infinite-scroll.svelte';
import ListingGrid from '$lib/components/common/listing-grid.svelte';
import AssetResultItem from '$lib/components/search/asset-result-item.svelte';
import EntryResultItem from '$lib/components/search/entry-result-item.svelte';
Expand All @@ -23,11 +23,11 @@
aria-rowcount={$searchResults.entries.length}
>
{#key $searchTerms}
{#each $searchResults.entries as entry (entry.id)}
{#await sleep(0) then}
<InfiniteScroll items={$searchResults.entries} itemKey="id">
{#snippet renderItem(/** @type {Entry} */ entry)}
<EntryResultItem {entry} />
{/await}
{/each}
{/snippet}
</InfiniteScroll>
{/key}
</ListingGrid>
{:else}
Expand All @@ -45,11 +45,11 @@
aria-rowcount={$searchResults.assets.length}
>
{#key $searchTerms}
{#each $searchResults.assets as asset (asset.path)}
{#await sleep(0) then}
<InfiniteScroll items={$searchResults.assets} itemKey="path">
{#snippet renderItem(/** @type {Asset} */ asset)}
<AssetResultItem {asset} />
{/await}
{/each}
{/snippet}
</InfiniteScroll>
{/key}
</ListingGrid>
{:else}
Expand Down

0 comments on commit 93daf21

Please sign in to comment.