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:
co-authored by
Cursor
parent
4c6acd5a3a
commit
019ce4e136
@@ -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>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user