diff --git a/src/components/App.tsx b/src/components/App.tsx index 75562f4ed..9e4cb05d4 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -1178,7 +1178,10 @@ class App extends React.Component { app={this} > {this.props.children} - +
diff --git a/src/components/MermaidToExcalidraw.tsx b/src/components/MermaidToExcalidraw.tsx index d3244c822..03f5c0fd6 100644 --- a/src/components/MermaidToExcalidraw.tsx +++ b/src/components/MermaidToExcalidraw.tsx @@ -1,34 +1,118 @@ -import { AppState } from "../types"; +import { useState, useRef, useEffect } from "react"; +import { AppState, BinaryFiles } from "../types"; import { updateActiveTool } from "../utils"; -import { useExcalidrawSetAppState } from "./App"; +import { useApp, useExcalidrawSetAppState } from "./App"; import { Button } from "./Button"; import { Dialog } from "./Dialog"; +import { + parseMermaid, + graphToExcalidraw, +} from "@excalidraw/mermaid-to-excalidraw"; import "./MermaidToExcalidraw.scss"; -const MermaidToExcalidraw = ({ appState }: { appState: AppState }) => { +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 MermaidToExcalidraw = ({ + appState, + elements, +}: { + appState: AppState; + elements: readonly NonDeletedExcalidrawElement[]; +}) => { + const [text, setText] = useState(""); + const [canvasData, setCanvasData] = useState<{ + //@ts-ignore + elements: readonly NonDeletedExcalidrawElement[]; + files: BinaryFiles | null; + }>({ elements: [], files: null }); + const canvasRef = useRef(null); + const app = useApp(); + + useEffect(() => { + 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: canvasData.elements, + files: canvasData.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); + }); + }); + }, [canvasData, canvasRef]); + const setAppState = useExcalidrawSetAppState(); if (appState?.activeTool?.type !== "mermaid") { return null; } + + const onChange = async (event: any) => { + setText(event.target.value); + let mermaidGraphData; + try { + mermaidGraphData = await parseMermaid(event.target.value, { + fontSize: DEFAULT_FONT_SIZE, + }); + } catch (e) { + // Parse error, displaying error message to users + } + + if (mermaidGraphData) { + const { elements, files } = graphToExcalidraw(mermaidGraphData); + + setCanvasData({ elements: convertToExcalidrawElements(elements), files }); + } + }; + + const onClose = () => { + const activeTool = updateActiveTool(appState, { type: "selection" }); + setAppState({ activeTool }); + }; + + const onSelect = () => { + app.scene.replaceAllElements([...elements, ...canvasData.elements]); + app.addFiles(Object.values(canvasData.files || [])); + app.scrollToContent(canvasData.elements); + onClose(); + }; + return ( - { - const activeTool = updateActiveTool(appState, { type: "selection" }); - setAppState({ activeTool }); - }} - title="Mermaid to Excalidraw" - > +
-