Skip to content

Commit aac9655

Browse files
authored
Add loading states for guestbook and photo feeds during server load (#688)
1 parent 2ec0e78 commit aac9655

File tree

10 files changed

+476
-245
lines changed

10 files changed

+476
-245
lines changed

astro.config.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import mdx from "@astrojs/mdx";
44
import netlify from "@astrojs/netlify";
55
import react from "@astrojs/react";
66
import sitemap from "@astrojs/sitemap";
7-
import { defineConfig } from "astro/config";
7+
import { defineConfig, fontProviders } from "astro/config";
88
import expressiveCode from "astro-expressive-code";
99
import autoprefixer from "autoprefixer";
1010
import cssnano from "cssnano";
@@ -49,26 +49,30 @@ export default defineConfig({
4949
experimental: {
5050
fonts: [
5151
{
52-
provider: "local",
52+
provider: fontProviders.local(),
5353
name: "HEX Franklin",
5454
cssVariable: "--font-sans",
5555
fallbacks: ["sans-serif"],
56-
variants: [
57-
{
58-
src: ["./src/fonts/HEX_Franklin_v0.3_Variable.woff2"],
59-
},
60-
],
56+
options: {
57+
variants: [
58+
{
59+
src: ["./src/fonts/HEX_Franklin_v0.3_Variable.woff2"],
60+
},
61+
],
62+
},
6163
},
6264
{
63-
provider: "local",
65+
provider: fontProviders.local(),
6466
name: "MonoLisa",
6567
cssVariable: "--font-mono",
6668
fallbacks: ["monospace"],
67-
variants: [
68-
{
69-
src: ["./src/fonts/MonoLisaVariableNormal.woff2"],
70-
},
71-
],
69+
options: {
70+
variants: [
71+
{
72+
src: ["./src/fonts/MonoLisaVariableNormal.woff2"],
73+
},
74+
],
75+
},
7276
},
7377
],
7478
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"@nanostores/react": "^1.0.0",
3434
"@types/react": "^19.2.8",
3535
"@types/react-dom": "^19.2.3",
36-
"astro": "^5.16.8",
36+
"astro": "^5.17.1",
3737
"astro-expressive-code": "^0.41.5",
3838
"astro-loading-indicator": "^0.7.1",
3939
"autoprefixer": "^10.4.23",

pnpm-lock.yaml

Lines changed: 223 additions & 60 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,22 @@
11
---
2-
import { db, desc, eq, Guestbook as GuestbookDB, isNull, or } from "astro:db";
32
import Carousel from "../Carousel.astro";
43
import CarouselButton from "../CarouselButton.astro";
54
import CarouselItem from "../CarouselItem.astro";
65
import Notecard from "../Notecard/Notecard.astro";
7-
8-
const LIMIT = 8;
9-
10-
const guestbook = await db
11-
.select()
12-
.from(GuestbookDB)
13-
.where(or(eq(GuestbookDB.isSpam, false), isNull(GuestbookDB.isSpam)))
14-
.orderBy(desc(GuestbookDB.timestamp))
15-
.limit(LIMIT);
6+
import GuestbookItems from "./GuestbookItems.astro";
167
---
178

189
<Carousel>
19-
{
20-
guestbook.map(({ author, url, content, timestamp, theme }) => (
21-
<CarouselItem>
22-
<Notecard
23-
author={author}
24-
url={url}
25-
content={content}
26-
timestamp={timestamp}
27-
theme={theme}
28-
small
29-
/>
30-
</CarouselItem>
31-
))
32-
}
10+
<GuestbookItems server:defer>
11+
<Fragment slot="fallback">
12+
{
13+
Array.from({ length: 8 }).map(() => (
14+
<CarouselItem>
15+
<Notecard content="" theme={1} small loading />
16+
</CarouselItem>
17+
))
18+
}
19+
</Fragment>
20+
</GuestbookItems>
3321
<CarouselButton url="/guestbook">See all messages</CarouselButton>
3422
</Carousel>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
import { db, desc, eq, Guestbook as GuestbookDB, isNull, or } from "astro:db";
3+
import CarouselItem from "../CarouselItem.astro";
4+
import Notecard from "../Notecard/Notecard.astro";
5+
6+
const LIMIT = 8;
7+
8+
const guestbook = await db
9+
.select()
10+
.from(GuestbookDB)
11+
.where(or(eq(GuestbookDB.isSpam, false), isNull(GuestbookDB.isSpam)))
12+
.orderBy(desc(GuestbookDB.timestamp))
13+
.limit(LIMIT);
14+
---
15+
16+
{
17+
guestbook.map(({ author, url, content, timestamp, theme }) => (
18+
<CarouselItem>
19+
<Notecard
20+
author={author}
21+
url={url}
22+
content={content}
23+
timestamp={timestamp}
24+
theme={theme}
25+
small
26+
/>
27+
</CarouselItem>
28+
))
29+
}
Lines changed: 15 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,31 @@
11
---
2-
import { getCollection } from "astro:content";
32
import Carousel from "../Carousel.astro";
43
import CarouselButton from "../CarouselButton.astro";
54
import CarouselItem from "../CarouselItem.astro";
6-
7-
const LIMIT = 12;
8-
9-
const entries = await getCollection("photos");
10-
const limitedEntries = entries.slice(0, LIMIT);
11-
12-
const photos = limitedEntries.map((entry) => entry.data);
5+
import PhotoItems from "./PhotoItems.astro";
136
---
147

158
<Carousel spacing="tight">
16-
{
17-
photos.map((photo) => (
18-
<CarouselItem>
19-
<a href={photo.url}>
20-
<img src={photo.image?.url} alt={photo.title} />
21-
</a>
22-
</CarouselItem>
23-
))
24-
}
9+
<PhotoItems server:defer>
10+
<Fragment slot="fallback">
11+
{
12+
Array.from({ length: 8 }).map(() => (
13+
<CarouselItem>
14+
<div class="skeleton" />
15+
</CarouselItem>
16+
))
17+
}
18+
</Fragment>
19+
</PhotoItems>
2520
<CarouselButton url="https://glass.photo/evadecker"
2621
>See all photos</CarouselButton
2722
>
2823
</Carousel>
2924

3025
<style>
31-
a {
32-
display: block;
33-
height: 220px;
34-
width: auto;
35-
}
36-
37-
img {
38-
display: block;
39-
height: 100%;
40-
width: auto;
41-
object-fit: cover;
26+
.skeleton {
4227
background-color: var(--gray-3);
28+
height: 220px;
29+
aspect-ratio: 5 / 3;
4330
}
4431
</style>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
import { getCollection } from "astro:content";
3+
import CarouselItem from "../CarouselItem.astro";
4+
5+
const LIMIT = 12;
6+
7+
const entries = await getCollection("photos");
8+
const limitedEntries = entries.slice(0, LIMIT);
9+
10+
const photos = limitedEntries.map((entry) => entry.data);
11+
---
12+
13+
{
14+
photos.map((photo) => (
15+
<CarouselItem>
16+
<a href={photo.url}>
17+
<img src={photo.image?.url} alt={photo.title} />
18+
</a>
19+
</CarouselItem>
20+
))
21+
}
22+
23+
<style>
24+
a {
25+
display: block;
26+
height: 220px;
27+
width: auto;
28+
}
29+
30+
img {
31+
display: block;
32+
height: 100%;
33+
width: auto;
34+
object-fit: cover;
35+
background-color: var(--gray-3);
36+
}
37+
</style>

src/components/Notecard/Notecard.astro

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import { NOTECARD_THEMES } from "./NotecardComposer";
55
interface Props {
66
content: string;
77
url?: string | null;
8-
author: string;
9-
timestamp: Date;
8+
author?: string;
9+
timestamp?: Date;
1010
theme: number;
1111
small?: boolean;
1212
randomTranslate?: boolean;
13+
loading?: boolean;
1314
}
1415
1516
const {
@@ -20,30 +21,37 @@ const {
2021
theme,
2122
small = false,
2223
randomTranslate = false,
24+
loading = false,
2325
} = Astro.props;
2426
2527
const rotation = `${Math.random() * 6 - 3}deg`;
2628
const translateX = randomTranslate ? `${Math.random() * 24 - 12}px` : 0;
2729
const backgroundImage = `url(${NOTECARD_THEMES[theme].src})`;
2830
---
2931

30-
<div class:list={["notecard", { small }]}>
32+
<div class:list={["notecard", { small, loading }]}>
3133
<pre class="content">{content}</pre>
3234
<div class="credit">
33-
<div class="author">
34-
{
35-
url ? (
36-
<a href={url} rel="external" data-fathom="author-link">
37-
{author}
38-
</a>
39-
) : (
40-
author
41-
)
42-
}
43-
</div>
44-
<div class="date">
45-
<RelativeDate date={timestamp} />
46-
</div>
35+
{
36+
author && (
37+
<div class="author">
38+
{url ? (
39+
<a href={url} rel="external" data-fathom="author-link">
40+
{author}
41+
</a>
42+
) : (
43+
author
44+
)}
45+
</div>
46+
)
47+
}
48+
{
49+
timestamp && (
50+
<div class="date">
51+
<RelativeDate date={timestamp} />
52+
</div>
53+
)
54+
}
4755
</div>
4856
</div>
4957

@@ -65,6 +73,10 @@ const backgroundImage = `url(${NOTECARD_THEMES[theme].src})`;
6573
&.small {
6674
font-size: 0.8em;
6775
}
76+
77+
&.loading {
78+
opacity: 0.5;
79+
}
6880
}
6981

7082
.content {

src/pages/guestbook.astro

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { db, Guestbook as GuestbookDB } from "astro:db";
33
import GuestbookEntries from "../components/Guestbook/GuestbookEntries.astro";
44
import GuestbookPagination from "../components/Guestbook/GuestbookPagination.astro";
5+
import Notecard from "../components/Notecard/Notecard.astro";
56
import { NotecardComposer } from "../components/Notecard/NotecardComposer";
67
import BaseLayout from "../layouts/BaseLayout.astro";
78
@@ -63,7 +64,15 @@ const description = "Leave a message in eva.town.";
6364
<h1 class="visually-hidden">Guestbook</h1>
6465
<div class="notecards">
6566
{isFirstPage && <NotecardComposer client:load />}
66-
<GuestbookEntries currentPage={currentPage} server:defer />
67+
<GuestbookEntries currentPage={currentPage} server:defer>
68+
<Fragment slot="fallback">
69+
{
70+
Array.from({ length: 24 }).map(() => (
71+
<Notecard theme={1} content="" loading />
72+
))
73+
}
74+
</Fragment>
75+
</GuestbookEntries>
6776
</div>
6877
<GuestbookPagination currentPage={currentPage} server:defer />
6978
</BaseLayout>

0 commit comments

Comments
 (0)