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[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 (
);
};
const MermaidToExcalidraw = ({
selectedElements,
}: {
selectedElements: readonly NonDeletedExcalidrawElement[];
}) => {
const mermaidToExcalidrawLib = useRef<{
parseMermaidToExcalidraw: (
defination: string,
options: MermaidOptions,
) => Promise;
} | null>(null);
const [text, setText] = useState("");
const deferredText = useDeferredValue(text);
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 selectedMermaidImage = selectedElements.filter(
(el) => el.type === "image" && el.customData?.mermaidText,
)[0];
const data = selectedMermaidImage
? selectedMermaidImage.customData?.mermaidText
: importMermaidDataFromStorage() || MERMAID_EXAMPLE;
setText(data);
}
}, [loading, selectedElements]);
useEffect(() => {
const renderExcalidrawPreview = async () => {
const canvasNode = canvasRef.current;
if (!canvasNode || !mermaidToExcalidrawLib.current) {
return;
}
try {
const { elements, files } =
await mermaidToExcalidrawLib.current.parseMermaidToExcalidraw(
deferredText,
{
fontSize: DEFAULT_FONT_SIZE,
},
);
setError(null);
data.current = {
elements: convertToExcalidrawElements(
elements.map((el) => {
if (el.type === "image") {
el.customData = { mermaidText: text };
}
return el;
}),
{
regenerateIds: 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);
} catch (e: any) {
console.error(e.message);
resetPreview();
if (deferredText) {
setError(e.message);
}
}
};
renderExcalidrawPreview();
}, [deferredText, text]);
const onClose = () => {
app.setActiveTool({ type: "selection" });
saveMermaidDataToStorage(text);
};
const onSelect = () => {
const { elements: newElements, files } = data.current;
app.addElementsFromPasteOrLibrary({
elements: newElements,
files,
position: "center",
fitToContent: true,
});
onClose();
};
return (
);
};
export default MermaidToExcalidraw;