Improve timeline UX and add catalog restore scripts for paintings and portraits.

Load the home-page catalog once with a lightweight artists API, batch pan/zoom updates per frame, use dynamic year labels, and speed up movement-flow zoom. Add sync-image-paths and fetch-artist-images plus docs for the post-seed pipeline.
This commit is contained in:
Danila Khodjaef
2026-07-03 18:33:16 +03:00
parent 7f26696328
commit 02d238b043
26 changed files with 865 additions and 195 deletions
+48 -35
View File
@@ -8,6 +8,7 @@ import ArtistBio from '../components/ArtistBio';
import CheckupPage from '../pages/CheckupPage';
import { api, type FixPaintingImageResult, type FixArtistPortraitResult } from '../api/client';
import type { TimelineData, Artist, ArtistDetail, Painting, PaintingDetail, MovementGalleryDetail } from '../types';
import { createViewChangeScheduler } from '../utils/timelineView';
import { sortArtistPaintingsChronological } from '../utils/paintingUtils';
import { readDebugMode, readDebugShowMore, writeDebugMode, writeDebugShowMore } from '../utils/debugMode';
import './HomePage.css';
@@ -119,18 +120,6 @@ export default function HomePage() {
} | null>(null);
const detailReturnToRef = useRef<View>({ type: 'timeline' });
useEffect(() => {
api.getBounds()
.then((b) => {
const min = b.min_year ?? -800;
const max = b.max_year ?? 2025;
setBounds({ min, max });
setViewStart(min);
setViewEnd(max);
})
.catch(() => {});
}, []);
useEffect(() => {
if (view.type === 'gallery') {
setGallerySession({ kind: 'artist', artistId: view.artistId, data: view.data });
@@ -141,31 +130,55 @@ export default function HomePage() {
}
}, [view]);
const loadTimelineData = useCallback(async (start: number, end: number) => {
try {
setLoading(true);
const [timeline, artistList] = await Promise.all([
api.getTimeline(start, end),
api.getArtists(start, end),
]);
setTimelineData(timeline);
setArtists(artistList);
setError(null);
} catch {
setError('Could not load gallery data. Is the server running?');
} finally {
setLoading(false);
}
// Load full catalog once — pan/zoom filters client-side (MovementBands, Timeline).
useEffect(() => {
let cancelled = false;
(async () => {
try {
setLoading(true);
const b = await api.getBounds();
const min = b.min_year ?? -800;
const max = b.max_year ?? 2025;
if (cancelled) return;
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);
setError(null);
} catch {
if (!cancelled) setError('Could not load gallery data. Is the server running?');
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => {
cancelled = true;
};
}, []);
useEffect(() => {
loadTimelineData(viewStart, viewEnd);
}, [viewStart, viewEnd, loadTimelineData]);
const viewChangeScheduler = useRef(
createViewChangeScheduler((start, end) => {
setViewStart(start);
setViewEnd(end);
})
);
const handleViewChange = (start: number, end: number) => {
setViewStart(start);
setViewEnd(end);
};
useEffect(() => () => viewChangeScheduler.current.cancel(), []);
const handleViewChange = useCallback((start: number, end: number) => {
viewChangeScheduler.current.schedule(start, end);
}, []);
const toggleDebugMode = () => {
setDebugMode((prev) => {
@@ -683,7 +696,7 @@ export default function HomePage() {
{error && <div className="error-banner">{error}</div>}
{loading ? (
{loading && timelineData.movements.length === 0 ? (
<div className="loading home-movements-section">Loading art history...</div>
) : (
<>