Improve 3D gallery and debug checkup workflow for image curation.

Add parquet floor, museum-style exit, movement-tinted walls, and gold/black
frames with gallery sync after Fix it. Debug panel gets Checked and Fix it
buttons; documentation and image-fetch reliability updates included.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Danila Khodjaef
2026-06-20 22:05:24 +03:00
co-authored by Cursor
parent 4c6acd5a3a
commit 019ce4e136
67 changed files with 1061 additions and 190 deletions
+95 -32
View File
@@ -5,7 +5,7 @@ import VirtualGallery from '../components/VirtualGallery';
import PaintingDetailView from '../components/PaintingDetail';
import ArtistBio from '../components/ArtistBio';
import CheckupPage from '../pages/CheckupPage';
import { api } from '../api/client';
import { api, type FixPaintingImageResult } from '../api/client';
import type { TimelineData, Artist, ArtistDetail, Painting, PaintingDetail } from '../types';
import { sortArtistPaintingsChronological } from '../utils/paintingUtils';
import { readDebugMode, writeDebugMode } from '../utils/debugMode';
@@ -18,6 +18,17 @@ type View =
| { type: 'painting'; paintingId: number; data: PaintingDetail; returnTo: View }
| { type: 'bio'; artistId: number; data: ArtistDetail; returnTo: View };
function patchPaintingInArtistDetail(
detail: ArtistDetail,
paintingId: number,
patch: Partial<Painting>
): ArtistDetail {
return {
...detail,
paintings: detail.paintings.map((p) => (p.id === paintingId ? { ...p, ...patch } : p)),
};
}
export default function HomePage() {
const [view, setView] = useState<View>({ type: 'timeline' });
const [gallerySession, setGallerySession] = useState<{ artistId: number; data: ArtistDetail } | null>(
@@ -31,6 +42,7 @@ export default function HomePage() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [detailArtistPaintings, setDetailArtistPaintings] = useState<Painting[]>([]);
const [imageRevisions, setImageRevisions] = useState<Record<number, number>>({});
const [debugMode, setDebugMode] = useState(readDebugMode);
const detailReturnToRef = useRef<View>({ type: 'timeline' });
@@ -88,42 +100,80 @@ export default function HomePage() {
});
};
const handlePaintingImageFixed = useCallback(async (paintingId: number) => {
const handlePaintingImageFixed = useCallback(async (paintingId: number, fixResult: FixPaintingImageResult) => {
const data = await api.getPainting(paintingId);
setView((current) =>
current.type === 'painting' && current.paintingId === paintingId
? { ...current, data }
: current
);
const patch: Partial<Painting> = {
image_path: fixResult.imagePath ?? data.painting.image_path,
thumbnail_path: fixResult.thumbnailPath ?? data.painting.thumbnail_path,
checkup_checked: fixResult.checked ?? true,
checkup_fixed: fixResult.fixed ?? true,
};
const updatedData: PaintingDetail = {
...data,
painting: { ...data.painting, ...patch },
};
setImageRevisions((prev) => ({ ...prev, [paintingId]: (prev[paintingId] ?? 0) + 1 }));
setView((current) => {
if (current.type !== 'painting' || current.paintingId !== paintingId) return current;
let returnTo = current.returnTo;
if (returnTo.type === 'gallery' && returnTo.artistId === data.painting.artist_id) {
returnTo = {
...returnTo,
data: patchPaintingInArtistDetail(returnTo.data, paintingId, patch),
};
}
return { ...current, data: updatedData, returnTo };
});
setDetailArtistPaintings((list) =>
list.map((p) =>
p.id === paintingId
? { ...p, image_path: data.painting.image_path, thumbnail_path: data.painting.thumbnail_path }
: p
)
list.map((p) => (p.id === paintingId ? { ...p, ...patch } : p))
);
if (gallerySession?.artistId === data.painting.artist_id) {
setGallerySession((session) =>
session && session.artistId === data.painting.artist_id
? { ...session, data: patchPaintingInArtistDetail(session.data, paintingId, patch) }
: session
);
}, []);
const handlePaintingCheckupFlagsUpdated = useCallback(
async (paintingId: number, flags: { checked: boolean; fixed: boolean }) => {
const data = await api.getPainting(paintingId);
const patch: Partial<Painting> = {
checkup_checked: flags.checked,
checkup_fixed: flags.fixed,
};
const updatedData: PaintingDetail = {
...data,
painting: { ...data.painting, ...patch },
};
setView((current) => {
if (current.type !== 'painting' || current.paintingId !== paintingId) return current;
let returnTo = current.returnTo;
if (returnTo.type === 'gallery' && returnTo.artistId === data.painting.artist_id) {
returnTo = {
...returnTo,
data: patchPaintingInArtistDetail(returnTo.data, paintingId, patch),
};
}
return { ...current, data: updatedData, returnTo };
});
setDetailArtistPaintings((list) =>
list.map((p) => (p.id === paintingId ? { ...p, ...patch } : p))
);
setGallerySession((session) =>
session
? {
...session,
data: {
...session.data,
paintings: session.data.paintings.map((p) =>
p.id === paintingId
? {
...p,
image_path: data.painting.image_path,
thumbnail_path: data.painting.thumbnail_path,
}
: p
),
},
}
session && session.artistId === data.painting.artist_id
? { ...session, data: patchPaintingInArtistDetail(session.data, paintingId, patch) }
: session
);
}
}, [gallerySession]);
},
[]
);
const handleArtistClick = async (artistId: number) => {
try {
@@ -210,6 +260,7 @@ export default function HomePage() {
<div className={galleryActive ? undefined : 'gallery-session-suspended'} aria-hidden={!galleryActive}>
<VirtualGallery
data={gallerySession.data}
imageRevisions={imageRevisions}
active={galleryActive}
onPaintingClick={handlePaintingClick}
onNavigateArtist={handleArtistClick}
@@ -224,7 +275,18 @@ export default function HomePage() {
<PaintingDetailView
data={view.data}
artistPaintings={sortedDetailArtistPaintings}
onBack={() => setView(view.returnTo)}
onBack={() => {
const returnTo = view.returnTo;
if (returnTo.type === 'gallery' && gallerySession?.artistId === returnTo.artistId) {
setView({
type: 'gallery',
artistId: gallerySession.artistId,
data: gallerySession.data,
});
} else {
setView(returnTo);
}
}}
onPaintingClick={handlePaintingClick}
onCatalogNavigate={handleCatalogNavigate}
onArtistBio={async () => {
@@ -234,6 +296,7 @@ export default function HomePage() {
onInfluenceArtistClick={handleArtistClick}
debugMode={debugMode}
onPaintingImageFixed={handlePaintingImageFixed}
onPaintingCheckupFlagsUpdated={handlePaintingCheckupFlagsUpdated}
/>
</div>
)}