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
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user