import { useState, useRef, useEffect, useDeferredValue } from "react"; import { BinaryFiles } from "../types"; import { useApp } from "./App"; import { Button } from "./Button"; import { Dialog } from "./Dialog"; 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"; import "./MermaidToExcalidraw.scss"; import { MermaidToExcalidrawResult } from "@excalidraw/mermaid-to-excalidraw/dist/interfaces"; import type { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw"; import { t } from "../i18n"; import Trans from "./Trans"; 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[Car]"; 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 = () => { const [mermaidToExcalidrawLib, setMermaidToExcalidrawLib] = useState<{ loaded: boolean; api: { parseMermaidToExcalidraw: ( defination: string, options: MermaidOptions, ) => Promise; } | null; }>({ loaded: false, api: null }); const [text, setText] = useState(""); const deferredText = useDeferredValue(text.trim()); 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 = ""; setError(null); canvasNode.replaceChildren(); }; useEffect(() => { const loadMermaidToExcalidrawLib = async () => { const api = await import( /* webpackChunkName:"mermaid-to-excalidraw" */ "@excalidraw/mermaid-to-excalidraw" ); setMermaidToExcalidrawLib({ loaded: true, api }); }; loadMermaidToExcalidrawLib(); }, []); useEffect(() => { const data = importMermaidDataFromStorage() || MERMAID_EXAMPLE; setText(data); }, []); useEffect(() => { const renderExcalidrawPreview = async () => { const canvasNode = canvasRef.current; const parent = canvasNode?.parentElement; if ( !mermaidToExcalidrawLib.loaded || !canvasNode || !parent || !mermaidToExcalidrawLib.api ) { return; } if (!deferredText) { resetPreview(); return; } try { const { elements, files } = await mermaidToExcalidrawLib.api.parseMermaidToExcalidraw( deferredText, { fontSize: DEFAULT_FONT_SIZE, }, ); setError(null); data.current = { elements: convertToExcalidrawElements(elements, { regenerateIds: true, }), files, }; const canvas = await exportToCanvas({ elements: data.current.elements, files: data.current.files, exportPadding: DEFAULT_EXPORT_PADDING, maxWidthOrHeight: Math.max(parent.offsetWidth, parent.offsetHeight) * window.devicePixelRatio, }); // 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 = "var(--default-bg-color)"; canvasNode.replaceChildren(canvas); } catch (e: any) { parent.style.background = "var(--default-bg-color)"; if (deferredText) { setError(e.message); } } }; renderExcalidrawPreview(); }, [deferredText, mermaidToExcalidrawLib]); const onClose = () => { app.setOpenDialog(null); saveMermaidDataToStorage(text); }; const onSelect = () => { const { elements: newElements, files } = data.current; app.addElementsFromPasteOrLibrary({ elements: newElements, files, position: "center", fitToContent: true, }); onClose(); }; return (

{t("mermaid.title")}

( {el} )} sequenceLink={(el) => ( {el} )} />
} >