From c1eeb42f2166f0e32a727c0f402a6e1cf47877c3 Mon Sep 17 00:00:00 2001 From: Julien Oculi Date: Tue, 2 Jul 2024 13:52:41 +0200 Subject: [PATCH] feat(island): :sparkles: add `BlogCardList` to fetch and display news progressively --- islands/BlogCardList.tsx | 89 ++++++++++++++++++++++++++++++++++++++++ routes/blog/index.tsx | 4 +- 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 islands/BlogCardList.tsx diff --git a/islands/BlogCardList.tsx b/islands/BlogCardList.tsx new file mode 100644 index 0000000..d64ee56 --- /dev/null +++ b/islands/BlogCardList.tsx @@ -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, + { limit, ac }: { limit?: number; ac?: AbortController }, +) { + ;(async () => { + const newsList = requestApiStream( + '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([]) + const ac = new AbortController() + + useEffect(() => { + fillList(list, { limit, ac }) + }) + + if (limit && usePlaceholder) { + const placeholders = Array + .from({ length: limit }) + .map((_, index) => ( + } + fallback={Fallback} + signal={ac.signal} + > + {updateFromList(list, index)} + + )) + return <>{placeholders} + } + + return <>{list} +} + +function Placeholder() { + return ( +
+

Chargement ...

+
+ ) +} + +function Fallback() { + return ( +
+

Pas de news disponible

+
+ ) +} + +function updateFromList( + list: Signal, + index: number, +): Promise { + const { promise, resolve } = Promise.withResolvers() + list.subscribe((value) => { + const selected = value.at(index) + if (selected) { + resolve(selected) + } + }) + + return promise +} diff --git a/routes/blog/index.tsx b/routes/blog/index.tsx index d7dbae8..811e2b5 100644 --- a/routes/blog/index.tsx +++ b/routes/blog/index.tsx @@ -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 ( <>

Nos articles

- {blogMock.map(BlogCard)} + )