Add movement artist filter modal and fix large-hall texture blanks
Clicking a movement opens ArtistFilterModal to choose artists before the gallery. Large halls no longer permanently blank frames when texture loads exceed the overlay deadline; fall back thumb to full to on-demand API. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
ad1572b3aa
commit
0a5918c4dd
@@ -12,7 +12,7 @@ import type {
|
||||
MovementArtistGroup,
|
||||
TourGalleryDetail,
|
||||
} from '../types';
|
||||
import { galleryImageUrlWithRevision, imageUrl, api } from '../api/client';
|
||||
import { galleryImageUrlCandidates, imageUrl, api } from '../api/client';
|
||||
import { comparePaintingsChronological, paintingHasCuratorNotes, paintingHasInfluenceLinks, paintingWallCaption } from '../utils/paintingUtils';
|
||||
import { cloneSurfaceTexture, getSurfaceTexture } from '../utils/galleryProceduralTextures';
|
||||
import { resolveMovementInteriorStyle, type MovementInteriorStyle, type GalleryWindowSpec } from '../data/movement-interior-styles';
|
||||
@@ -647,20 +647,32 @@ function CanvasCover({
|
||||
);
|
||||
}
|
||||
|
||||
function usePaintingTexture(url: string | null) {
|
||||
function usePaintingTexture(urls: string[] | string | null) {
|
||||
const candidates = useMemo(() => {
|
||||
const list = Array.isArray(urls) ? urls.filter(Boolean) : urls ? [urls] : [];
|
||||
return list;
|
||||
}, [Array.isArray(urls) ? urls.join('|') : urls ?? '']);
|
||||
const candidateKey = candidates.join('|');
|
||||
const [urlIndex, setUrlIndex] = useState(0);
|
||||
const url = candidates[urlIndex] ?? null;
|
||||
const [texture, setTexture] = useState<THREE.Texture | null>(null);
|
||||
const [failed, setFailed] = useState(!url);
|
||||
const [failed, setFailed] = useState(candidates.length === 0);
|
||||
const textureLoad = useContext(GalleryTextureLoadContext);
|
||||
const { gl } = useThree();
|
||||
|
||||
useEffect(() => {
|
||||
setUrlIndex(0);
|
||||
}, [candidateKey]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!url) {
|
||||
setTexture(null);
|
||||
setFailed(true);
|
||||
setFailed(candidates.length === 0 || urlIndex >= candidates.length);
|
||||
return;
|
||||
}
|
||||
|
||||
setFailed(false);
|
||||
setTexture(null);
|
||||
let disposed = false;
|
||||
let loaded: THREE.Texture | null = null;
|
||||
let settled = false;
|
||||
@@ -674,9 +686,12 @@ function usePaintingTexture(url: string | null) {
|
||||
};
|
||||
|
||||
textureLoad?.begin();
|
||||
// Release the loading overlay after a deadline, but do NOT mark the texture
|
||||
// failed — large halls (e.g. Byzantine, 45 works) queue behind ~6 browser
|
||||
// connections and often finish after 10s. Marking failed permanently left
|
||||
// blank canvases even when the image arrived later.
|
||||
const loadTimeout = window.setTimeout(() => {
|
||||
if (settled || disposed) return;
|
||||
setFailed(true);
|
||||
finish();
|
||||
}, TEXTURE_LOAD_TIMEOUT_MS);
|
||||
|
||||
@@ -692,17 +707,21 @@ function usePaintingTexture(url: string | null) {
|
||||
const img = tex.image as HTMLImageElement | undefined;
|
||||
if (!img || img.width < 4 || img.height < 4) {
|
||||
tex.dispose();
|
||||
setFailed(true);
|
||||
finish();
|
||||
if (!disposed) {
|
||||
if (urlIndex + 1 < candidates.length) setUrlIndex((i) => i + 1);
|
||||
else setFailed(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
loaded = tex;
|
||||
tex.colorSpace = THREE.SRGBColorSpace;
|
||||
tex.anisotropy = 4;
|
||||
// Release the hall overlay counter before GPU upload — large halls
|
||||
// (50+ works) otherwise stay on "Loading paintings…" for a long time.
|
||||
finish();
|
||||
if (!disposed) setTexture(tex);
|
||||
if (!disposed) {
|
||||
setFailed(false);
|
||||
setTexture(tex);
|
||||
}
|
||||
try {
|
||||
if (!disposed) gl.initTexture(tex);
|
||||
} catch {
|
||||
@@ -713,7 +732,10 @@ function usePaintingTexture(url: string | null) {
|
||||
() => {
|
||||
window.clearTimeout(loadTimeout);
|
||||
finish();
|
||||
if (!disposed) setFailed(true);
|
||||
if (!disposed) {
|
||||
if (urlIndex + 1 < candidates.length) setUrlIndex((i) => i + 1);
|
||||
else setFailed(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -724,7 +746,7 @@ function usePaintingTexture(url: string | null) {
|
||||
loaded?.dispose();
|
||||
setTexture(null);
|
||||
};
|
||||
}, [url, textureLoad, gl]);
|
||||
}, [url, urlIndex, candidates.length, textureLoad, gl]);
|
||||
|
||||
return { texture, failed };
|
||||
}
|
||||
@@ -877,8 +899,8 @@ function PaintingFrame({
|
||||
const reviewed = paintingIsReviewed(painting);
|
||||
const { matBorder, rail, depth: frameDepth } = frameDimsForReviewed(reviewed);
|
||||
const hasImage = paintingHasGalleryImage(painting);
|
||||
const url = hasImage ? galleryImageUrlWithRevision(painting, imageRevision) : null;
|
||||
const { texture, failed } = usePaintingTexture(url);
|
||||
const urls = hasImage ? galleryImageUrlCandidates(painting, imageRevision) : [];
|
||||
const { texture, failed } = usePaintingTexture(urls);
|
||||
const showImage = hasImage && !failed && !!texture;
|
||||
const showCanvas = !showImage;
|
||||
const hasInfluenceLinks = paintingHasInfluenceLinks(painting);
|
||||
|
||||
Reference in New Issue
Block a user