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"; const LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW = "mermaid-to-excalidraw"; 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 MermaidToExcalidraw = ({ appState, elements, }: { appState: AppState; elements: readonly NonDeletedExcalidrawElement[]; }) => { const mermaidToExcalidrawLib = useRef(null); const [text, setText] = useState(""); const [loading, setLoading] = useState(true); const canvasRef = useRef(null); const data = useRef<{ elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles | null; }>({ elements: [], files: null }); const app = useApp(); 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(); if (data) { setText(data); } } }, [loading]); useEffect(() => { const convertMermaidToExcal = async () => { let mermaidGraphData; try { mermaidGraphData = await mermaidToExcalidrawLib.current.parseMermaid( text, { fontSize: DEFAULT_FONT_SIZE, }, ); } catch (e) { // Parse error, displaying error message to users } if (mermaidGraphData) { const { elements, files } = mermaidToExcalidrawLib.current.graphToExcalidraw(mermaidGraphData); data.current = { elements: convertToExcalidrawElements(elements), files, }; const canvasNode = canvasRef.current; if (!canvasNode) { return; } const maxWidth = canvasNode.offsetWidth; const maxHeight = canvasNode.offsetHeight; let dimension = Math.max(maxWidth, maxHeight); if (dimension > canvasNode.offsetWidth) { dimension = canvasNode.offsetWidth - 10; } if (dimension > canvasNode.offsetHeight) { dimension = canvasNode.offsetHeight; } exportToCanvas({ elements: data.current.elements, files: data.current.files, exportPadding: DEFAULT_EXPORT_PADDING, maxWidthOrHeight: dimension, }).then((canvas) => { // if converting to blob fails, there's some problem that will // likely prevent preview and export (e.g. canvas too big) return canvasToBlob(canvas).then(() => { canvasNode.replaceChildren(canvas); }); }); } }; convertMermaidToExcal(); }, [text]); 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 (