feat(island): add BlogCardList to fetch and display news progressively

This commit is contained in:
Julien Oculi 2024-07-02 13:52:41 +02:00
parent 72281ae551
commit c1eeb42f21
2 changed files with 91 additions and 2 deletions

89
islands/BlogCardList.tsx Normal file
View file

@ -0,0 +1,89 @@
import { BlogCard, BlogProps } from ':components/BlogBlocks.tsx'
import Suspense from ':islands/Suspens.tsx'
import { requestApiStream } from ':src/utils.ts'
import { Signal, useSignal } from '@preact/signals'
import type { JSX } from 'preact'
import { useEffect } from 'preact/hooks'
function fillList(
list: Signal<JSX.Element[]>,
{ limit, ac }: { limit?: number; ac?: AbortController },
) {
;(async () => {
const newsList = requestApiStream<void, BlogProps>(
'news/fetchAll',
'GET',
)
for await (const news of newsList) {
list.value = [
...list.value,
BlogCard({ ...news, lastUpdate: new Date(news.lastUpdate) }),
]
if (limit && list.value.length >= limit) break
}
ac?.abort()
})()
}
export default function BlogCardList(
{ limit, usePlaceholder }: { usePlaceholder?: boolean; limit?: number },
) {
const list = useSignal<JSX.Element[]>([])
const ac = new AbortController()
useEffect(() => {
fillList(list, { limit, ac })
})
if (limit && usePlaceholder) {
const placeholders = Array
.from({ length: limit })
.map((_, index) => (
<Suspense
loader={<Placeholder />}
fallback={Fallback}
signal={ac.signal}
>
{updateFromList(list, index)}
</Suspense>
))
return <>{placeholders}</>
}
return <>{list}</>
}
function Placeholder() {
return (
<div class='components__blog_block components__blog_block--card components__blog_block--placeholder'>
<h3>Chargement ...</h3>
</div>
)
}
function Fallback() {
return (
<div
class='components__blog_block components__blog_block--card components__blog_block--fallback'
inert
>
<h3>Pas de news disponible</h3>
</div>
)
}
function updateFromList(
list: Signal<JSX.Element[]>,
index: number,
): Promise<JSX.Element> {
const { promise, resolve } = Promise.withResolvers<JSX.Element>()
list.subscribe((value) => {
const selected = value.at(index)
if (selected) {
resolve(selected)
}
})
return promise
}

View file

@ -1,12 +1,12 @@
import { AutoGrid } from ':components/AutoGrid.tsx'
import { BlogCard, blogMock } from ':components/BlogCard.tsx'
import BlogCardList from ':islands/BlogCardList.tsx'
export default function Blog() {
return (
<>
<h1>Nos articles</h1>
<AutoGrid columnWidth='15rem'>
{blogMock.map(BlogCard)}
<BlogCardList />
</AutoGrid>
</>
)