import React, { useEffect, useRef, useState } from "react"; import { Box, Skeleton, Typography } from "@mui/material"; import { cvBuilderApi, CvTheme, getCvPageMetrics } from "../cvBuilder"; import { useI18n } from "../i18n/I18nProvider"; // Only the server's synthetic template samples are cached, never a user's CV. const samples = new Map>(); function sample(themeId: string) { let pending = samples.get(themeId); if (!pending) { pending = cvBuilderApi.themePreview(themeId).then(result => { if (typeof result.html !== "string") throw new Error("Template preview unavailable"); return result.html; }).catch(error => { samples.delete(themeId); throw error; }); samples.set(themeId, pending); } return pending; } export default function CvTemplateThumbnail({ theme, height = 150 }: { theme?: CvTheme; height?: number }) { const { t } = useI18n(); const host = useRef(null); const [visible, setVisible] = useState(typeof IntersectionObserver === "undefined"); const [html, setHtml] = useState<{ id: string; value: string } | null>(null); const [failed, setFailed] = useState(false); const [width, setWidth] = useState(210 * height / 297); const metrics = getCvPageMetrics("a4"); const scale = Math.min(width / metrics.widthPx, height / metrics.heightPx); useEffect(() => { const element = host.current; if (!element || typeof ResizeObserver === "undefined") return; const resize = new ResizeObserver(() => setWidth(element.clientWidth)); resize.observe(element); return () => resize.disconnect(); }, []); useEffect(() => { if (visible || !host.current || typeof IntersectionObserver === "undefined") return; const observer = new IntersectionObserver(entries => { if (entries.some(entry => entry.isIntersecting)) { setVisible(true); observer.disconnect(); } }, { rootMargin: "200px" }); observer.observe(host.current); return () => observer.disconnect(); }, [visible]); useEffect(() => { if (!visible || !theme?.id) return; let alive = true; setFailed(false); sample(theme.id).then(value => { if (alive) setHtml({ id: theme.id, value }); }) .catch(() => { if (alive) setFailed(true); }); return () => { alive = false; }; }, [theme?.id, visible]); const current = html?.id === theme?.id ? html?.value : null; return {current ?