import { useState, useRef, useEffect } from "react"; import { AppState, BinaryFiles } from "../types"; import { updateActiveTool } from "../utils"; import { useApp, useExcalidrawSetAppState } from "./App"; import { Button } from "./Button"; import { Dialog } from "./Dialog"; import "./MermaidToExcalidraw.scss"; import { DEFAULT_EXPORT_PADDING, DEFAULT_FONT_SIZE } from "../constants"; import { convertToExcalidrawElements, exportToCanvas, } from "../packages/excalidraw/index"; import { NonDeletedExcalidrawElement } from "../element/types"; import { canvasToBlob } from "../data/blob"; import { ArrowRightIcon } from "./icons"; import Spinner from "./Spinner"; const LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW = "mermaid-to-excalidraw"; const MERMAID_EXAMPLE = "flowchart TD\n A[Christmas] -->|Get money| B(Go shopping)\n B --> C{Let me think}\n C -->|One| D[Laptop]\n C -->|Two| E[iPhone]\n C -->|Three| F[test]"; const saveMermaidDataToStorage = (data: string) => { try { localStorage.setItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW, data); } catch (error: any) { // Unable to access window.localStorage console.error(error); } }; const importMermaidDataFromStorage = () => { try { const data = localStorage.getItem(LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW); if (data) { return data; } } catch (error: any) { // Unable to access localStorage console.error(error); } return null; }; const ErrorComp = ({ error }: { error: string }) => { return (
Error!

{error}

); }; const MermaidToExcalidraw = ({ appState, elements, }: { appState: AppState; elements: readonly NonDeletedExcalidrawElement[]; }) => { const mermaidToExcalidrawLib = useRef(null); const [text, setText] = useState(""); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const canvasRef = useRef(null); const data = useRef<{ elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles | null; }>({ elements: [], files: null }); const app = useApp(); const resetPreview = () => { const canvasNode = canvasRef.current; if (!canvasNode) { return; } const parent = canvasNode.parentElement; if (!parent) { return; } parent.style.background = ""; canvasNode.replaceChildren(); }; useEffect(() => { const loadMermaidToExcalidrawLib = async () => { mermaidToExcalidrawLib.current = await import( /* webpackChunkName:"mermaid-to-excalidraw" */ "@excalidraw/mermaid-to-excalidraw" ); setLoading(false); }; loadMermaidToExcalidrawLib(); }, []); useEffect(() => { if (!loading) { const data = importMermaidDataFromStorage() || MERMAID_EXAMPLE; setText(data); } }, [loading]); useEffect(() => { const renderExcalidrawPreview = async () => { let mermaidGraphData; const canvasNode = canvasRef.current; if (!canvasNode) { return; } try { mermaidGraphData = await mermaidToExcalidrawLib.current.parseMermaid( text, { fontSize: DEFAULT_FONT_SIZE, }, ); setError(null); } catch (e: any) { console.error(e.message); resetPreview(); if (text) { setError(e.message); } } if (mermaidGraphData) { const { elements, files } = mermaidToExcalidrawLib.current.graphToExcalidraw(mermaidGraphData); data.current = { elements: convertToExcalidrawElements(elements, appState, { regenerateIds: true, transformViewportToSceneCoords: true, }), files, }; const parent = canvasNode.parentElement!; const maxWidth = parent.offsetWidth; const maxHeight = parent.offsetHeight; let dimension = Math.max(maxWidth, maxHeight); dimension = Math.min(dimension, parent.offsetWidth - 10); dimension = Math.min(dimension, parent.offsetHeight - 10); const canvas = await exportToCanvas({ elements: data.current.elements, files: data.current.files, exportPadding: DEFAULT_EXPORT_PADDING, maxWidthOrHeight: dimension, }); // if converting to blob fails, there's some problem that will // likely prevent preview and export (e.g. canvas too big) await canvasToBlob(canvas); parent.style.background = "#fff"; canvasNode.replaceChildren(canvas); } }; renderExcalidrawPreview(); }, [text, appState]); const setAppState = useExcalidrawSetAppState(); const onClose = () => { const activeTool = updateActiveTool(appState, { type: "selection" }); setAppState({ activeTool }); saveMermaidDataToStorage(text); }; const onSelect = () => { const { elements: newElements, files } = data.current; app.scene.replaceAllElements([...elements, ...newElements]); app.addFiles(Object.values(files || [])); app.scrollToContent(newElements); app.setSelection(newElements); onClose(); }; return (

Mermaid to Excalidraw

Currently only flowcharts are supported. The other types will be rendered as image in Excalidraw.
Refer to the{" "} docs to get started.
} >