import React, { useEffect, useState, useRef } from "react"; import mermaid from "mermaid"; import { Download, Info } from "@phosphor-icons/react"; import { Button } from "@/components/ui/button"; interface MermaidProps { chart: string; } const Mermaid: React.FC = ({ chart }) => { const [mermaidError, setMermaidError] = useState(null); const [mermaidId] = useState(`mermaid-chart-${Math.random().toString(12).substring(7)}`); const elementRef = useRef(null); useEffect(() => { mermaid.initialize({ startOnLoad: false, }); mermaid.parseError = (error) => { console.error("Mermaid errors:", error); // Extract error message from error object // Parse error message safely let errorMessage; try { errorMessage = typeof error === "string" ? JSON.parse(error) : error; } catch (e) { errorMessage = error?.toString() || "Unknown error"; } console.log("Mermaid error message:", errorMessage); if (errorMessage.str !== "element is null") { setMermaidError( "Something went wrong while rendering the diagram. Please try again later or downvote the message if the issue persists.", ); } else { setMermaidError(null); } }; mermaid.contentLoaded(); }, []); const handleExport = async () => { if (!elementRef.current) return; try { // Get SVG element const svgElement = elementRef.current.querySelector("svg"); if (!svgElement) throw new Error("No SVG found"); // Get SVG viewBox dimensions const viewBox = svgElement.getAttribute("viewBox")?.split(" ").map(Number) || [ 0, 0, 0, 0, ]; const [, , viewBoxWidth, viewBoxHeight] = viewBox; // Create canvas with viewBox dimensions const canvas = document.createElement("canvas"); const scale = 2; // For better resolution canvas.width = viewBoxWidth * scale; canvas.height = viewBoxHeight * scale; const ctx = canvas.getContext("2d"); if (!ctx) throw new Error("Failed to get canvas context"); // Convert SVG to data URL const svgData = new XMLSerializer().serializeToString(svgElement); const svgBlob = new Blob([svgData], { type: "image/svg+xml;charset=utf-8" }); const svgUrl = URL.createObjectURL(svgBlob); // Create and load image const img = new Image(); img.src = svgUrl; await new Promise((resolve, reject) => { img.onload = () => { // Scale context for better resolution ctx.scale(scale, scale); ctx.drawImage(img, 0, 0, viewBoxWidth, viewBoxHeight); canvas.toBlob((blob) => { if (!blob) { reject(new Error("Failed to create blob")); return; } const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = `mermaid-diagram-${Date.now()}.png`; a.click(); // Cleanup URL.revokeObjectURL(url); URL.revokeObjectURL(svgUrl); resolve(true); }, "image/png"); }; img.onerror = () => reject(new Error("Failed to load SVG")); }); } catch (error) { console.error("Error exporting diagram:", error); setMermaidError("Failed to export diagram"); } }; useEffect(() => { if (elementRef.current) { elementRef.current.removeAttribute("data-processed"); mermaid .run({ nodes: [elementRef.current], }) .then(() => { setMermaidError(null); }) .catch((error) => { let errorMessage; try { errorMessage = typeof error === "string" ? JSON.parse(error) : error; } catch (e) { errorMessage = error?.toString() || "Unknown error"; } console.log("Mermaid error message:", errorMessage); if (errorMessage.str !== "element is null") { setMermaidError( "Something went wrong while rendering the diagram. Please try again later or downvote the message if the issue persists.", ); } else { setMermaidError(null); } }); } }, [chart]); return (
{mermaidError ? ( <>
{mermaidError}
{chart} ) : (
{chart}
)} {!mermaidError && ( )}
); }; export default Mermaid;