Add gallery loading indicators and recover from WebGL context loss.
- Show loading markers while the catalog, portrait thumbnails, and 3D halls load so users know loading is still in progress. - Recover the 3D hall from a lost WebGL context by remounting the canvas with a fresh context instead of leaving a permanent dark window, and guard the HDR environment map behind an error boundary. - Show movement streams whose span overlaps the view even when their artists lived outside the visible time range. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
99b8559607
commit
1408811948
@@ -1,12 +1,13 @@
|
||||
import { useState, useEffect, useCallback, useMemo, useRef, lazy, Suspense } from 'react';
|
||||
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import Timeline from '../components/Timeline';
|
||||
import TimelineEventGuides from '../components/TimelineEventGuides';
|
||||
import MovementBands from '../components/MovementBands';
|
||||
const VirtualGallery = lazy(() => import('../components/VirtualGallery'));
|
||||
import VirtualGallery from '../components/VirtualGallery';
|
||||
import PaintingDetailView from '../components/PaintingDetail';
|
||||
import ArtistBio from '../components/ArtistBio';
|
||||
import CheckupPage from '../pages/CheckupPage';
|
||||
import CuratorLoginModal from '../components/CuratorLoginModal';
|
||||
import GalleryLoadingMarker from '../components/GalleryLoadingMarker';
|
||||
import '../components/CuratorLoginModal.css';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import { api, type FixPaintingImageResult, type FixArtistPortraitResult } from '../api/client';
|
||||
@@ -108,6 +109,8 @@ export default function HomePage() {
|
||||
const [timelineData, setTimelineData] = useState<TimelineData>({ eras: [], movements: [] });
|
||||
const [artists, setArtists] = useState<Artist[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [portraitsLoading, setPortraitsLoading] = useState(false);
|
||||
const [galleryEntryLoading, setGalleryEntryLoading] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [detailArtistPaintings, setDetailArtistPaintings] = useState<Painting[]>([]);
|
||||
const [imageRevisions, setImageRevisions] = useState<Record<number, number>>({});
|
||||
@@ -399,21 +402,27 @@ export default function HomePage() {
|
||||
}, []);
|
||||
|
||||
const handleArtistClick = async (artistId: number) => {
|
||||
setGalleryEntryLoading('Opening artist gallery…');
|
||||
try {
|
||||
await api.preloadArtistImages(artistId).catch(() => undefined);
|
||||
const data = await api.getArtist(artistId);
|
||||
openArtistGallery(artistId, data);
|
||||
} catch {
|
||||
setError('Failed to load artist gallery.');
|
||||
} finally {
|
||||
setGalleryEntryLoading(null);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMovementClick = async (movementId: number) => {
|
||||
setGalleryEntryLoading('Opening movement gallery…');
|
||||
try {
|
||||
const data = await api.getMovementGallery(movementId);
|
||||
openMovementGallery(movementId, data);
|
||||
} catch {
|
||||
setError('Failed to load movement gallery.');
|
||||
} finally {
|
||||
setGalleryEntryLoading(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -599,9 +608,8 @@ export default function HomePage() {
|
||||
aria-hidden={!galleryActive}
|
||||
>
|
||||
{displayGallery.kind === 'artist' ? (
|
||||
<Suspense fallback={null}>
|
||||
<VirtualGallery
|
||||
key={`artist-${displayGallery.artistId}-${galleryRevision}-${galleryActive ? 'live' : 'parked'}`}
|
||||
key={`artist-${displayGallery.artistId}-${galleryRevision}`}
|
||||
mode="artist"
|
||||
data={displayGallery.data}
|
||||
imageRevisions={imageRevisions}
|
||||
@@ -617,11 +625,9 @@ export default function HomePage() {
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
<Suspense fallback={null}>
|
||||
<VirtualGallery
|
||||
key={`movement-${displayGallery.movementId}-${galleryRevision}-${galleryActive ? 'live' : 'parked'}`}
|
||||
key={`movement-${displayGallery.movementId}-${galleryRevision}`}
|
||||
mode="movement"
|
||||
data={displayGallery.data}
|
||||
imageRevisions={imageRevisions}
|
||||
@@ -629,7 +635,6 @@ export default function HomePage() {
|
||||
onPaintingClick={handlePaintingClick}
|
||||
onBack={() => setView({ type: 'timeline' })}
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -787,6 +792,16 @@ export default function HomePage() {
|
||||
</header>
|
||||
|
||||
<div className="home-timeline-stack">
|
||||
{loading && (
|
||||
<GalleryLoadingMarker overlay message="Loading art history…" />
|
||||
)}
|
||||
{galleryEntryLoading && (
|
||||
<GalleryLoadingMarker overlay message={galleryEntryLoading} />
|
||||
)}
|
||||
{portraitsLoading && !loading && !galleryEntryLoading && (
|
||||
<GalleryLoadingMarker banner message="Loading portraits…" />
|
||||
)}
|
||||
|
||||
<Timeline
|
||||
eras={timelineData.eras}
|
||||
viewStart={viewStart}
|
||||
@@ -799,9 +814,7 @@ export default function HomePage() {
|
||||
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
|
||||
{loading && timelineData.movements.length === 0 ? (
|
||||
<div className="loading home-movements-section">Loading art history...</div>
|
||||
) : (
|
||||
{!loading && (
|
||||
<>
|
||||
<TimelineEventGuides viewStart={viewStart} viewEnd={viewEnd} />
|
||||
<div className="home-movements-section">
|
||||
@@ -817,6 +830,7 @@ export default function HomePage() {
|
||||
onArtistClick={handleArtistClick}
|
||||
onMovementClick={handleMovementClick}
|
||||
onArtistHover={setHoveredLifespan}
|
||||
onPortraitsLoadingChange={setPortraitsLoading}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user