Speed up timeline load with portrait thumbs, bootstrap API, and caching.

Add catalog bootstrap endpoint, portrait thumbnail pipeline, lazy queued timeline images, gzip compression, and 3D texture throttling with code-split VirtualGallery.
This commit is contained in:
Danila Khodjaef
2026-07-06 14:27:29 +03:00
parent bdddadc4d6
commit 99b8559607
117 changed files with 646 additions and 171 deletions
+12 -14
View File
@@ -1,8 +1,8 @@
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { useState, useEffect, useCallback, useMemo, useRef, lazy, Suspense } from 'react';
import Timeline from '../components/Timeline';
import TimelineEventGuides from '../components/TimelineEventGuides';
import MovementBands from '../components/MovementBands';
import VirtualGallery from '../components/VirtualGallery';
const VirtualGallery = lazy(() => import('../components/VirtualGallery'));
import PaintingDetailView from '../components/PaintingDetail';
import ArtistBio from '../components/ArtistBio';
import CheckupPage from '../pages/CheckupPage';
@@ -144,23 +144,16 @@ export default function HomePage() {
(async () => {
try {
setLoading(true);
const b = await api.getBounds();
const min = b.min_year ?? -800;
const max = b.max_year ?? 2025;
const catalog = await api.getCatalogBootstrap();
if (cancelled) return;
const min = catalog.bounds.min_year ?? -800;
const max = catalog.bounds.max_year ?? 2025;
setBounds({ min, max });
setViewStart(min);
setViewEnd(max);
const [timeline, artistList] = await Promise.all([
api.getTimeline(min, max),
api.getTimelineArtists(),
]);
if (cancelled) return;
setTimelineData(timeline);
setArtists(artistList);
setTimelineData({ eras: catalog.eras, movements: catalog.movements });
setArtists(catalog.artists);
setError(null);
} catch {
if (!cancelled) setError('Could not load gallery data. Is the server running?');
@@ -407,6 +400,7 @@ export default function HomePage() {
const handleArtistClick = async (artistId: number) => {
try {
await api.preloadArtistImages(artistId).catch(() => undefined);
const data = await api.getArtist(artistId);
openArtistGallery(artistId, data);
} catch {
@@ -605,6 +599,7 @@ export default function HomePage() {
aria-hidden={!galleryActive}
>
{displayGallery.kind === 'artist' ? (
<Suspense fallback={null}>
<VirtualGallery
key={`artist-${displayGallery.artistId}-${galleryRevision}-${galleryActive ? 'live' : 'parked'}`}
mode="artist"
@@ -622,7 +617,9 @@ export default function HomePage() {
})
}
/>
</Suspense>
) : (
<Suspense fallback={null}>
<VirtualGallery
key={`movement-${displayGallery.movementId}-${galleryRevision}-${galleryActive ? 'live' : 'parked'}`}
mode="movement"
@@ -632,6 +629,7 @@ export default function HomePage() {
onPaintingClick={handlePaintingClick}
onBack={() => setView({ type: 'timeline' })}
/>
</Suspense>
)}
</div>
)}