import { MermaidOptions } from "@excalidraw/mermaid-to-excalidraw"; import { MermaidToExcalidrawResult } from "@excalidraw/mermaid-to-excalidraw/dist/interfaces"; import { DEFAULT_EXPORT_PADDING, DEFAULT_FONT_SIZE } from "../../constants"; import { convertToExcalidrawElements, exportToCanvas, } from "../../packages/excalidraw/index"; import { NonDeletedExcalidrawElement } from "../../element/types"; import { AppClassProperties, BinaryFiles } from "../../types"; import { canvasToBlob } from "../../data/blob"; import { atom } from "jotai"; export const resetPreview = ({ canvasRef, setError, }: { canvasRef: React.RefObject; setError: (error: Error | null) => void; }) => { const canvasNode = canvasRef.current; if (!canvasNode) { return; } const parent = canvasNode.parentElement; if (!parent) { return; } parent.style.background = ""; setError(null); canvasNode.replaceChildren(); }; export type OnTestSubmitRetValue = { rateLimit?: number | null; rateLimitRemaining?: number | null; } & ( | { generatedResponse: any | string | undefined; error?: null | undefined; } | { error: Error; generatedResponse?: null | undefined; } ); export interface CommonDialogProps { onTextSubmit( value: string, type: "text-to-diagram" | "text-to-drawing", ): Promise; } export interface MermaidToExcalidrawLibProps { loaded: boolean; api: Promise<{ parseMermaidToExcalidraw: ( definition: string, options: MermaidOptions, ) => Promise; }>; } interface ConvertMermaidToExcalidrawFormatProps { canvasRef: React.RefObject; mermaidToExcalidrawLib: MermaidToExcalidrawLibProps; mermaidDefinition: string; setError: (error: Error | null) => void; data: React.MutableRefObject<{ elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles | null; }>; } export const convertMermaidToExcalidraw = async ({ canvasRef, mermaidToExcalidrawLib, mermaidDefinition, setError, data, }: ConvertMermaidToExcalidrawFormatProps) => { const canvasNode = canvasRef.current; const parent = canvasNode?.parentElement; if (!canvasNode || !parent) { return; } if (!mermaidDefinition) { resetPreview({ canvasRef, setError }); return; } try { const api = await mermaidToExcalidrawLib.api; let ret; try { ret = await api.parseMermaidToExcalidraw(mermaidDefinition, { fontSize: DEFAULT_FONT_SIZE, }); } catch (err: any) { ret = await api.parseMermaidToExcalidraw( mermaidDefinition.replace(/"/g, "'"), { fontSize: DEFAULT_FONT_SIZE, }, ); } const { elements, files } = ret; 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 (err: any) { console.error(err); parent.style.background = "var(--default-bg-color)"; if (mermaidDefinition) { setError(err); } throw err; } }; export const LOCAL_STORAGE_KEY_MERMAID_TO_EXCALIDRAW = "mermaid-to-excalidraw"; export 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); } }; export const insertToEditor = ({ app, data, text, shouldSaveMermaidDataToStorage, }: { app: AppClassProperties; data: { elements: readonly NonDeletedExcalidrawElement[]; files: BinaryFiles | null; }; text?: string; shouldSaveMermaidDataToStorage?: boolean; }) => { const { elements: newElements, files } = data; if (!newElements.length) { return; } app.addElementsFromPasteOrLibrary({ elements: newElements, files, position: "center", fitToContent: true, }); app.setOpenDialog(null); if (shouldSaveMermaidDataToStorage && text) { saveMermaidDataToStorage(text); } }; export const MIN_PROMPT_LENGTH = 3; export const MAX_PROMPT_LENGTH = 1000; export const rateLimitsAtom = atom<{ rateLimit: number; rateLimitRemaining: number; } | null>(null);