Improve 3D gallery UX, placeholders, and image accuracy.
Add multi-row dynamic halls, eye-level camera, canvas covers for missing works, preserved view when returning from detail, and corrected image overrides for Kauffman and Raphael. Update documentation and add fetched painting assets. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
08f99d7a29
commit
792a0b1a77
@@ -16,6 +16,9 @@ type View =
|
||||
|
||||
export default function HomePage() {
|
||||
const [view, setView] = useState<View>({ type: 'timeline' });
|
||||
const [gallerySession, setGallerySession] = useState<{ artistId: number; data: ArtistDetail } | null>(
|
||||
null
|
||||
);
|
||||
const [bounds, setBounds] = useState({ min: -800, max: 2025 });
|
||||
const [viewStart, setViewStart] = useState(-800);
|
||||
const [viewEnd, setViewEnd] = useState(2025);
|
||||
@@ -36,6 +39,14 @@ export default function HomePage() {
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (view.type === 'gallery') {
|
||||
setGallerySession({ artistId: view.artistId, data: view.data });
|
||||
} else if (view.type === 'timeline') {
|
||||
setGallerySession(null);
|
||||
}
|
||||
}, [view]);
|
||||
|
||||
const loadTimelineData = useCallback(async (start: number, end: number) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
@@ -65,6 +76,7 @@ export default function HomePage() {
|
||||
const handleArtistClick = async (artistId: number) => {
|
||||
try {
|
||||
const data = await api.getArtist(artistId);
|
||||
setGallerySession({ artistId, data });
|
||||
setView({ type: 'gallery', artistId, data });
|
||||
} catch {
|
||||
setError('Failed to load artist gallery.');
|
||||
@@ -89,71 +101,80 @@ export default function HomePage() {
|
||||
setView({ type: 'bio', artistId: artistData.artist.id, data: artistData, returnTo });
|
||||
};
|
||||
|
||||
if (view.type === 'gallery') {
|
||||
return (
|
||||
<VirtualGallery
|
||||
data={view.data}
|
||||
onPaintingClick={handlePaintingClick}
|
||||
onNavigateArtist={handleArtistClick}
|
||||
onBack={() => setView({ type: 'timeline' })}
|
||||
onBioClick={() => handleBioClick(view.data, view)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (view.type === 'painting') {
|
||||
return (
|
||||
<PaintingDetailView
|
||||
data={view.data}
|
||||
onBack={() => setView(view.returnTo)}
|
||||
onPaintingClick={handlePaintingClick}
|
||||
onArtistBio={async () => {
|
||||
const artistData = await api.getArtist(view.data.painting.artist_id);
|
||||
setView({ type: 'bio', artistId: artistData.artist.id, data: artistData, returnTo: view });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (view.type === 'bio') {
|
||||
return (
|
||||
<ArtistBio
|
||||
artist={view.data.artist}
|
||||
onBack={() => setView(view.returnTo)}
|
||||
onEnterGallery={() => setView({ type: 'gallery', artistId: view.artistId, data: view.data })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const galleryActive = view.type === 'gallery';
|
||||
|
||||
return (
|
||||
<div className="home-page">
|
||||
<header className="site-header">
|
||||
<h1>Virtual Art Gallery</h1>
|
||||
<p className="site-subtitle">Explore the flowing connections of art history</p>
|
||||
</header>
|
||||
|
||||
<Timeline
|
||||
eras={timelineData.eras}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
onViewChange={handleViewChange}
|
||||
absoluteMin={bounds.min}
|
||||
absoluteMax={bounds.max}
|
||||
/>
|
||||
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
|
||||
{loading ? (
|
||||
<div className="loading">Loading art history...</div>
|
||||
) : (
|
||||
<MovementBands
|
||||
movements={timelineData.movements}
|
||||
artists={artists}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
onArtistClick={handleArtistClick}
|
||||
/>
|
||||
<>
|
||||
{gallerySession && (
|
||||
<div className={galleryActive ? undefined : 'gallery-session-suspended'} aria-hidden={!galleryActive}>
|
||||
<VirtualGallery
|
||||
data={gallerySession.data}
|
||||
active={galleryActive}
|
||||
onPaintingClick={handlePaintingClick}
|
||||
onNavigateArtist={handleArtistClick}
|
||||
onBack={() => setView({ type: 'timeline' })}
|
||||
onBioClick={() => handleBioClick(gallerySession.data, { type: 'gallery', ...gallerySession })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{view.type === 'painting' && (
|
||||
<div className="home-overlay">
|
||||
<PaintingDetailView
|
||||
data={view.data}
|
||||
onBack={() => setView(view.returnTo)}
|
||||
onPaintingClick={handlePaintingClick}
|
||||
onArtistBio={async () => {
|
||||
const artistData = await api.getArtist(view.data.painting.artist_id);
|
||||
setView({ type: 'bio', artistId: artistData.artist.id, data: artistData, returnTo: view });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{view.type === 'bio' && (
|
||||
<div className="home-overlay">
|
||||
<ArtistBio
|
||||
artist={view.data.artist}
|
||||
onBack={() => setView(view.returnTo)}
|
||||
onEnterGallery={() =>
|
||||
setView({ type: 'gallery', artistId: view.artistId, data: view.data })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{view.type === 'timeline' && (
|
||||
<div className="home-page">
|
||||
<header className="site-header">
|
||||
<h1>Virtual Art Gallery</h1>
|
||||
<p className="site-subtitle">Explore the flowing connections of art history</p>
|
||||
</header>
|
||||
|
||||
<Timeline
|
||||
eras={timelineData.eras}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
onViewChange={handleViewChange}
|
||||
absoluteMin={bounds.min}
|
||||
absoluteMax={bounds.max}
|
||||
/>
|
||||
|
||||
{error && <div className="error-banner">{error}</div>}
|
||||
|
||||
{loading ? (
|
||||
<div className="loading">Loading art history...</div>
|
||||
) : (
|
||||
<MovementBands
|
||||
movements={timelineData.movements}
|
||||
artists={artists}
|
||||
viewStart={viewStart}
|
||||
viewEnd={viewEnd}
|
||||
onArtistClick={handleArtistClick}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user