返回 presentation-ai
ImageWithFallback.tsx
根目录 / src / components / ui / ImageWithFallback.tsx
1 "use client";
2
3 import { useState, type ImgHTMLAttributes } from "react";
4
5 interface ImageWithFallbackProps extends Omit<
6 ImgHTMLAttributes<HTMLImageElement>,
7 "src" | "onError"
8 > {
9 src: string;
10 fallbackSrc?: string;
11 fallbackElement?: React.ReactNode;
12 }
13
14 /**
15 * An image component that tries the primary src first, then falls back to fallbackSrc if it fails.
16 * If both fail (or no fallbackSrc is provided), it renders the fallbackElement.
17 */
18 export function ImageWithFallback({
19 src,
20 fallbackSrc,
21 fallbackElement,
22 alt,
23 ...props
24 }: ImageWithFallbackProps) {
25 const [currentSrc, setCurrentSrc] = useState(src);
26 const [hasError, setHasError] = useState(false);
27 const [triedFallback, setTriedFallback] = useState(false);
28
29 const handleError = () => {
30 if (!triedFallback && fallbackSrc) {
31 setCurrentSrc(fallbackSrc);
32 setTriedFallback(true);
33 } else {
34 setHasError(true);
35 }
36 };
37
38 if (hasError) {
39 return fallbackElement ?? null;
40 }
41
42 return (
43 /* biome-ignore lint/performance/noImgElement: Intentionally using img for external URLs with fallback mechanism */
44 <img src={currentSrc} alt={alt} onError={handleError} {...props} />
45 );
46 }
47
48 /**
49 * Generates the alternative extension URL for an image.
50 * If the image ends with .png, returns .jpg version and vice versa.
51 * Handles URLs with query parameters.
52 */
53 export function getAlternateImageUrl(imageUrl: string): string {
54 // Remove query parameters for extension check, but preserve them
55 const parts = imageUrl.split("?");
56 const baseUrl = parts[0];
57 const queryParams = parts[1];
58
59 if (!baseUrl) {
60 return "";
61 }
62
63 if (baseUrl.endsWith(".png")) {
64 const newBase = `${baseUrl.slice(0, -4)}.jpg`;
65 return queryParams ? `${newBase}?${queryParams}` : newBase;
66 }
67
68 if (baseUrl.endsWith(".jpg") || baseUrl.endsWith(".jpeg")) {
69 const extension = baseUrl.endsWith(".jpeg") ? ".jpeg" : ".jpg";
70 const newBase = `${baseUrl.slice(0, -extension.length)}.png`;
71 return queryParams ? `${newBase}?${queryParams}` : newBase;
72 }
73
74 // If no known extension, return empty string
75 return "";
76 }
77
77 lines Plain Text