Add debug More/Clear/Upload tools for paintings and artist portraits.
Extends the debug panel on painting detail and artist bio with a 20-result search picker, local image upload, and clear-to-empty-frame workflow, plus API routes, artist checkup migration, and documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
b4425445bb
commit
0972b5df99
@@ -0,0 +1,101 @@
|
||||
import { useEffect } from 'react';
|
||||
import { debugImageProxyUrl, type DebugImageSearchManyResult, type DebugImageSearchResultItem } from '../api/client';
|
||||
import './DebugSearchResultsModal.css';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
title: string;
|
||||
data: DebugImageSearchManyResult | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
applyingUrl: string | null;
|
||||
onClose: () => void;
|
||||
onSelect: (item: DebugImageSearchResultItem) => void;
|
||||
}
|
||||
|
||||
export default function DebugSearchResultsModal({
|
||||
open,
|
||||
title,
|
||||
data,
|
||||
loading,
|
||||
error,
|
||||
applyingUrl,
|
||||
onClose,
|
||||
onSelect,
|
||||
}: Props) {
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
|
||||
const onKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === 'Escape') onClose();
|
||||
};
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, [open, onClose]);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="debug-search-modal-overlay"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label={title}
|
||||
onClick={onClose}
|
||||
>
|
||||
<div className="debug-search-modal" onClick={(e) => e.stopPropagation()}>
|
||||
<header className="debug-search-modal-header">
|
||||
<div>
|
||||
<h3>{title}</h3>
|
||||
{data?.query && <p className="debug-search-modal-query">{data.query}</p>}
|
||||
</div>
|
||||
<button type="button" className="debug-search-modal-close" onClick={onClose} aria-label="Close">
|
||||
×
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{loading && <p className="debug-search-modal-status">Loading results…</p>}
|
||||
{error && <p className="debug-search-modal-error">{error}</p>}
|
||||
|
||||
{!loading && !error && data && data.results.length === 0 && (
|
||||
<p className="debug-search-modal-status">No images found.</p>
|
||||
)}
|
||||
|
||||
{!loading && data && data.results.length > 0 && (
|
||||
<>
|
||||
<p className="debug-search-modal-hint">Click an image to replace the current one.</p>
|
||||
<div className="debug-search-modal-grid">
|
||||
{data.results.map((item, index) => {
|
||||
const busy = applyingUrl === item.imageUrl;
|
||||
return (
|
||||
<button
|
||||
key={`${item.imageUrl}-${index}`}
|
||||
type="button"
|
||||
className={`debug-search-modal-item${busy ? ' debug-search-modal-item-busy' : ''}`}
|
||||
disabled={!!applyingUrl}
|
||||
onClick={() => onSelect(item)}
|
||||
title="Use this image"
|
||||
>
|
||||
<img
|
||||
src={debugImageProxyUrl(item.thumbUrl || item.imageUrl, {
|
||||
searchUrl: data.searchUrl,
|
||||
source: item.source,
|
||||
})}
|
||||
alt={`Result ${index + 1}`}
|
||||
loading="lazy"
|
||||
onError={(e) => {
|
||||
(e.target as HTMLImageElement).src = '/placeholder-art.svg';
|
||||
}}
|
||||
/>
|
||||
<span className="debug-search-modal-item-index">{index + 1}</span>
|
||||
{busy && <span className="debug-search-modal-item-busy-label">Saving…</span>}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user