Add movement gallery wings with period interiors and expand timeline features.
Movement galleries split large catalogs into chronological wings (~55 works), use era-themed 3D interiors with side-wall windows, wing navigator on the back exit, and front archways between wings. Also adds painting annotations, timeline event guides, portrait hover highlights, and documentation/API updates. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
0972b5df99
commit
df29848d89
@@ -1,7 +1,57 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { debugImageProxyUrl, type DebugImageSearchManyResult, type DebugImageSearchResultItem } from '../api/client';
|
||||
import './DebugSearchResultsModal.css';
|
||||
|
||||
function formatResolution(width?: number, height?: number): string | null {
|
||||
if (!width || !height || width <= 0 || height <= 0) return null;
|
||||
return `${width} × ${height}`;
|
||||
}
|
||||
|
||||
function ResultResolution({
|
||||
item,
|
||||
searchUrl,
|
||||
}: {
|
||||
item: DebugImageSearchResultItem;
|
||||
searchUrl: string;
|
||||
}) {
|
||||
const initial = formatResolution(item.width, item.height);
|
||||
const [label, setLabel] = useState<string | null>(initial);
|
||||
|
||||
useEffect(() => {
|
||||
if (initial) {
|
||||
setLabel(initial);
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
if (cancelled) return;
|
||||
const text = formatResolution(img.naturalWidth, img.naturalHeight);
|
||||
setLabel(text ?? '—');
|
||||
};
|
||||
img.onerror = () => {
|
||||
if (!cancelled) setLabel('—');
|
||||
};
|
||||
img.src = debugImageProxyUrl(item.imageUrl, {
|
||||
searchUrl,
|
||||
source: item.source,
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
img.onload = null;
|
||||
img.onerror = null;
|
||||
};
|
||||
}, [item.imageUrl, item.source, item.width, item.height, searchUrl, initial]);
|
||||
|
||||
return (
|
||||
<span className="debug-search-modal-item-resolution" aria-hidden="true">
|
||||
{label ?? '…'}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
title: string;
|
||||
@@ -76,19 +126,22 @@ export default function DebugSearchResultsModal({
|
||||
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>}
|
||||
<span className="debug-search-modal-item-media">
|
||||
<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>}
|
||||
</span>
|
||||
<ResultResolution item={item} searchUrl={data.searchUrl} />
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
Reference in New Issue
Block a user