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
+62 -22
View File
@@ -1,6 +1,6 @@
import { useEffect, useState, type SyntheticEvent } from 'react';
import type { InfluenceLink, Painting, PaintingDetail } from '../types';
import { api, debugImageProxyUrl, imageUrl, paintingImageUrl, type DebugImageSearchResult } from '../api/client';
import { api, debugImageProxyUrl, imageUrl, paintingImageUrl, type DebugImageSearchResult, type FixPaintingImageResult } from '../api/client';
import PaintingLightbox from './PaintingLightbox';
import './PaintingDetail.css';
@@ -13,7 +13,14 @@ interface Props {
onArtistBio: () => void;
onInfluenceArtistClick?: (artistId: number) => void;
debugMode?: boolean;
onPaintingImageFixed?: (paintingId: number) => void | Promise<void>;
onPaintingImageFixed?: (
paintingId: number,
fixResult: FixPaintingImageResult
) => void | Promise<void>;
onPaintingCheckupFlagsUpdated?: (
paintingId: number,
flags: { checked: boolean; fixed: boolean }
) => void | Promise<void>;
}
function influenceKey(inf: InfluenceLink, index: number): string {
@@ -180,6 +187,7 @@ export default function PaintingDetailView({
onInfluenceArtistClick,
debugMode = false,
onPaintingImageFixed,
onPaintingCheckupFlagsUpdated,
}: Props) {
const { painting, influencedBy, influenced } = data;
const [fullscreen, setFullscreen] = useState(false);
@@ -188,6 +196,7 @@ export default function PaintingDetailView({
const [debugLoading, setDebugLoading] = useState(false);
const [debugError, setDebugError] = useState<string | null>(null);
const [fixing, setFixing] = useState(false);
const [markingChecked, setMarkingChecked] = useState(false);
const imageSrc = `${paintingImageUrl(painting)}${paintingImageUrl(painting).includes('?') ? '&' : '?'}v=${imageVersion}`;
@@ -237,9 +246,15 @@ export default function PaintingDetailView({
setFixing(true);
setDebugError(null);
try {
await api.fixPaintingImage(painting.id, debugSearch.imageUrl);
const fixResult = await api.fixPaintingImage(painting.id, debugSearch.imageUrl, {
searchUrl: debugSearch.searchUrl,
source: debugSearch.source,
thumbUrl: debugSearch.thumbUrl,
});
setImageVersion((v) => v + 1);
await onPaintingImageFixed?.(painting.id);
if (onPaintingImageFixed) {
await onPaintingImageFixed(painting.id, fixResult);
}
} catch (err) {
setDebugError(err instanceof Error ? err.message : 'Could not replace image.');
} finally {
@@ -247,6 +262,20 @@ export default function PaintingDetailView({
}
};
const handleMarkChecked = async () => {
if (painting.checkup_checked || markingChecked) return;
setMarkingChecked(true);
setDebugError(null);
try {
const updated = await api.updatePaintingCheckupFlags(painting.id, { checked: true });
await onPaintingCheckupFlagsUpdated?.(painting.id, updated);
} catch (err) {
setDebugError(err instanceof Error ? err.message : 'Could not mark as checked.');
} finally {
setMarkingChecked(false);
}
};
useEffect(() => {
if (fullscreen) return;
@@ -391,28 +420,39 @@ export default function PaintingDetailView({
{debugLoading && <p className="debug-image-status">Searching</p>}
{debugError && <p className="debug-image-error">{debugError}</p>}
{!debugLoading && debugSearch?.imageUrl && (
<>
<img
className="debug-image-preview"
src={debugImageProxyUrl(debugSearch.imageUrl)}
alt={`Google search result for ${debugSearch.query}`}
onError={(e) => {
(e.target as HTMLImageElement).src = '/placeholder-art.svg';
}}
/>
<button
type="button"
className="debug-fix-btn"
onClick={handleFixImage}
disabled={fixing}
>
{fixing ? 'Replacing…' : 'Fix it'}
</button>
</>
<img
className="debug-image-preview"
src={debugImageProxyUrl(debugSearch.imageUrl, {
searchUrl: debugSearch.searchUrl,
source: debugSearch.source,
})}
alt={`Google search result for ${debugSearch.query}`}
onError={(e) => {
(e.target as HTMLImageElement).src = '/placeholder-art.svg';
}}
/>
)}
{!debugLoading && debugSearch && !debugSearch.imageUrl && !debugError && (
<p className="debug-image-status">No Google image result found.</p>
)}
<div className="debug-action-buttons">
<button
type="button"
className="debug-checked-btn"
onClick={handleMarkChecked}
disabled={!!painting.checkup_checked || markingChecked}
>
{markingChecked ? '…' : 'Checked'}
</button>
<button
type="button"
className="debug-fix-btn"
onClick={handleFixImage}
disabled={fixing || debugLoading || !debugSearch?.imageUrl}
>
{fixing ? '…' : 'Fix it'}
</button>
</div>
</aside>
)}