import { blurhashToImageCssObject } from "@unpic/placeholder"; import * as React from "react"; import { transformUrl } from "unpic"; import { useDebugEvents } from "../../utils"; import { TooltipSymbol } from "../Tooltip"; import * as styles from "./styles.module.css"; const configDeviceSizes = [ 480, // old phones 640, // older and lower-end phones 750, // iPhone 6-8 828, // iPhone XR/11 960, // older horizontal phones 1080, // iPhone 6-8 Plus 1280, // 720p 1668, // Various iPads 1920, // 1080p 2048, // QXGA 2560, // WQXGA 3200, // QHD+ 3840, // 4K 4480, // 4.5K 5120, // 5K 6016, // 6K ]; const configImageSizes = [16, 32, 64, 128, 256]; const allSizes = [...configImageSizes, ...configDeviceSizes]; export type SrcContext = { src: string; height?: number; width?: number; blurhash?: string; dominantColor?: string; }; export const Image = React.forwardRef(function Image( { image, altText, critical, omitBlurhash, sizes = "100vw", crossOrigin, ...rest }: { image: string | SrcContext; omitBlurhash?: boolean; altText: string; critical?: boolean; sizes: string; className: string; crossOrigin?: boolean; "data-id": string; "data-label"?: string; } & React.ImgHTMLAttributes, ref: React.ForwardedRef, ) { const [hasError, setHasError] = React.useState(false); const [hasLoaded, setHasLoaded] = React.useState(false); const attrs = useDebugEvents(rest); const src = typeof image === "string" ? image : image?.src; const width = typeof image === "string" ? undefined : image?.width; const height = typeof image === "string" ? undefined : image?.height; const blurhash = typeof image === "string" ? undefined : image?.blurhash; const dominantColor = typeof image === "string" ? undefined : image?.dominantColor; const aspectRatio = height && width ? height / width : undefined; const { hostname, pathname } = React.useMemo( () => src ? src.startsWith("/") ? process.env.PREVIEW ? { pathname: src, fallbackPath: src, hostname: "__replace__", } : { pathname: `/cdn-cgi/image/f=auto${src}`, fallbackPath: src, hostname: "__replace__", } : new URL(src) : ({} as URL), [src], ); const props: React.ImgHTMLAttributes = { src: src, width: width, height: height, sizes: sizes, onLoad: () => { setHasLoaded(true); }, }; if (altText) { props.alt = altText; } else { props.role = "presentation"; } // if the customer doesn't pay for the image optimization service, we fallback to the original image if ( pathname?.startsWith("/cdn-cgi/image/f=auto") || hostname === "assets.devize.com" || hostname === "assets.brevity.io" ) { props.onError = ({ currentTarget }) => { setHasLoaded(true); if (hasError) { return; } currentTarget.onerror = null; currentTarget.src = currentTarget.src.replace( /\/cdn-cgi\/image\/(.*?)\//g, "/", ); currentTarget.srcset = ""; currentTarget.sizes = ""; setHasError(true); }; } // High priority images should be loaded eagerly if (critical) { props.loading = "eager"; (props as any).fetchPriority = "high"; } else { // Otherwise use native lazy loading and async decoding props.loading = "lazy"; props.decoding = "async"; } if (hostname === "assets.devize.com" || hostname === "assets.brevity.io") { props.src = `https://assets.devize.com/cdn-cgi/image/f=auto${pathname}`; props.crossOrigin = "anonymous"; } if (hostname === "__replace__") { props.crossOrigin = "anonymous"; } if (props.src) { props.srcSet = allSizes .map((size) => { const transformed = transformUrl({ url: hostname === "__replace__" ? `https://${hostname}${pathname}` : (props.src as string), width: size, height: aspectRatio ? Math.round(aspectRatio * size) : undefined, }); if (transformed) { return `${transformed.toString()} ${size}w`; } return ""; }) .join(",\n") .replaceAll("https://__replace__", ""); } if (crossOrigin) { props.crossOrigin = "anonymous"; } if (blurhash && !hasLoaded && !omitBlurhash) { const blurStyles = blurhashToImageCssObject(blurhash, 7, 4); props.style = { ...blurStyles, ...props.style, }; } if (dominantColor && !hasLoaded && !omitBlurhash) { props.style = { backgroundColor: dominantColor, ...props.style, }; } return (
{props.src ? ( ) : null}
); }); Image[TooltipSymbol] = true;