import { NodeViewProps, NodeViewWrapper } from '@tiptap/react'; import { ActionIcon, Button, Card, Group, Image, Text, useComputedColorScheme, } from '@mantine/core'; import { useState } from 'react'; import { uploadFile } from '@/features/page/services/page-service.ts'; import { svgStringToFile } from '@/lib'; import { useDisclosure } from '@mantine/hooks'; import { getFileUrl } from '@/lib/config.ts'; import { ExcalidrawImperativeAPI } from '@excalidraw/excalidraw/types/types'; import { IAttachment } from '@/lib/types'; import ReactClearModal from 'react-clear-modal'; import clsx from 'clsx'; import { IconEdit } from '@tabler/icons-react'; import { lazy } from 'react'; import { Suspense } from 'react'; const Excalidraw = lazy(() => import('@excalidraw/excalidraw').then((module) => ({ default: module.Excalidraw, })) ); export default function ExcalidrawView(props: NodeViewProps) { const { node, updateAttributes, editor, selected } = props; const { src, title, width, attachmentId } = node.attrs; const [excalidrawAPI, setExcalidrawAPI] = useState(null); const [excalidrawData, setExcalidrawData] = useState(null); const [opened, { open, close }] = useDisclosure(false); const computedColorScheme = useComputedColorScheme(); const handleOpen = async () => { if (!editor.isEditable) { return; } try { if (src) { const url = getFileUrl(src); const request = await fetch(url, { credentials: 'include', cache: 'no-store', }); const { loadFromBlob } = await import('@excalidraw/excalidraw'); const data = await loadFromBlob(await request.blob(), null, null); setExcalidrawData(data); } } catch (err) { console.error(err); } finally { open(); } }; const handleSave = async () => { if (!excalidrawAPI) { return; } const { exportToSvg } = await import('@excalidraw/excalidraw'); const svg = await exportToSvg({ elements: excalidrawAPI?.getSceneElements(), appState: { exportEmbedScene: true, exportWithDarkMode: computedColorScheme != 'light', }, files: excalidrawAPI?.getFiles(), }); const serializer = new XMLSerializer(); let svgString = serializer.serializeToString(svg); svgString = svgString.replace( /https:\/\/unpkg\.com\/@excalidraw\/excalidraw@undefined/g, 'https://unpkg.com/@excalidraw/excalidraw@latest' ); const fileName = 'diagram.excalidraw.svg'; const excalidrawSvgFile = await svgStringToFile(svgString, fileName); const pageId = editor.storage?.pageId; let attachment: IAttachment = null; if (attachmentId) { attachment = await uploadFile(excalidrawSvgFile, pageId, attachmentId); } else { attachment = await uploadFile(excalidrawSvgFile, pageId); } updateAttributes({ src: `/files/${attachment.id}/${attachment.fileName}?t=${new Date(attachment.updatedAt).getTime()}`, title: attachment.fileName, size: attachment.fileSize, attachmentId: attachment.id, }); close(); }; return (
setExcalidrawAPI(api)} initialData={{ ...excalidrawData, scrollToContent: true, }} theme={computedColorScheme} />
{src ? (
e.detail === 2 && handleOpen()} radius="md" fit="contain" w={width} src={getFileUrl(src)} alt={title} className={clsx( selected ? 'ProseMirror-selectednode' : '', 'alignCenter' )} /> {selected && ( )}
) : ( e.detail === 2 && handleOpen()} p="xs" style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', }} withBorder className={clsx(selected ? 'ProseMirror-selectednode' : '')} >
Double-click to edit Excalidraw diagram
)}
); }