import dedent from 'dedent'; const mermaid = dedent(`import React, { useEffect, useRef, useState } from "react"; import { TransformWrapper, TransformComponent, ReactZoomPanPinchRef, } from "react-zoom-pan-pinch"; import mermaid from "mermaid"; import { ZoomIn, ZoomOut, RefreshCw } from "lucide-react"; import { Button } from "/components/ui/button"; interface MermaidDiagramProps { content: string; } const MermaidDiagram: React.FC = ({ content }) => { const mermaidRef = useRef(null); const transformRef = useRef(null); const [isRendered, setIsRendered] = useState(false); useEffect(() => { mermaid.initialize({ startOnLoad: false, theme: "base", themeVariables: { background: "#282C34", primaryColor: "#333842", secondaryColor: "#333842", tertiaryColor: "#333842", primaryTextColor: "#ABB2BF", secondaryTextColor: "#ABB2BF", lineColor: "#636D83", fontSize: "16px", nodeBorder: "#636D83", mainBkg: '#282C34', altBackground: '#282C34', textColor: '#ABB2BF', edgeLabelBackground: '#282C34', clusterBkg: '#282C34', clusterBorder: "#636D83", labelBoxBkgColor: "#333842", labelBoxBorderColor: "#636D83", labelTextColor: "#ABB2BF", }, flowchart: { curve: "basis", nodeSpacing: 50, rankSpacing: 50, diagramPadding: 8, htmlLabels: true, useMaxWidth: true, defaultRenderer: "dagre-d3", padding: 15, wrappingWidth: 200, }, }); const renderDiagram = async () => { if (mermaidRef.current) { try { const { svg } = await mermaid.render("mermaid-diagram", content); mermaidRef.current.innerHTML = svg; const svgElement = mermaidRef.current.querySelector("svg"); if (svgElement) { svgElement.style.width = "100%"; svgElement.style.height = "100%"; const pathElements = svgElement.querySelectorAll("path"); pathElements.forEach((path) => { path.style.strokeWidth = "1.5px"; }); const rectElements = svgElement.querySelectorAll("rect"); rectElements.forEach((rect) => { const parent = rect.parentElement; if (parent && parent.classList.contains("node")) { rect.style.stroke = "#636D83"; rect.style.strokeWidth = "1px"; } else { rect.style.stroke = "none"; } }); } setIsRendered(true); } catch (error) { console.error("Mermaid rendering error:", error); mermaidRef.current.innerHTML = "Error rendering diagram"; } } }; renderDiagram(); }, [content]); const centerAndFitDiagram = () => { if (transformRef.current && mermaidRef.current) { const { centerView, zoomToElement } = transformRef.current; zoomToElement(mermaidRef.current as HTMLElement); centerView(1, 0); } }; useEffect(() => { if (isRendered) { centerAndFitDiagram(); } }, [isRendered]); const handlePanning = () => { if (transformRef.current) { const { state, instance } = transformRef.current; if (!state) { return; } const { scale, positionX, positionY } = state; const { wrapperComponent, contentComponent } = instance; if (wrapperComponent && contentComponent) { const wrapperRect = wrapperComponent.getBoundingClientRect(); const contentRect = contentComponent.getBoundingClientRect(); const maxX = wrapperRect.width - contentRect.width * scale; const maxY = wrapperRect.height - contentRect.height * scale; let newX = positionX; let newY = positionY; if (newX > 0) { newX = 0; } if (newY > 0) { newY = 0; } if (newX < maxX) { newX = maxX; } if (newY < maxY) { newY = maxY; } if (newX !== positionX || newY !== positionY) { instance.setTransformState(scale, newX, newY); } } } }; return (
{({ zoomIn, zoomOut }) => ( <>
)}
); }; export default MermaidDiagram;`); const wrapMermaidDiagram = (content: string) => { return dedent(`import React from 'react'; import MermaidDiagram from '/components/ui/MermaidDiagram'; export default App = () => ( ); `); }; export const getMermaidFiles = (content: string) => { return { 'App.tsx': wrapMermaidDiagram(content), 'index.tsx': dedent(`import React, { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import "./styles.css"; import App from "./App"; const root = createRoot(document.getElementById("root")); root.render(); ;`), '/components/ui/MermaidDiagram.tsx': mermaid, }; };