import React, { useEffect, useRef, useState } from 'react';
import { Transformer } from 'markmap-lib';
import { Markmap } from 'markmap-view';
import { Spin, Empty, Button, Space } from 'antd';
import { FullscreenOutlined, ZoomInOutlined, ZoomOutOutlined, SyncOutlined } from '@ant-design/icons';
const transformer = new Transformer();
const hasRenderableSize = (element) => {
if (!element) return false;
const rect = element.getBoundingClientRect();
return Number.isFinite(rect.width) && Number.isFinite(rect.height) && rect.width > 0 && rect.height > 0;
};
const MindMap = ({ content }) => {
const svgRef = useRef(null);
const markmapRef = useRef(null);
const latestRootRef = useRef(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (!content || !svgRef.current) return;
setLoading(true);
try {
const { root } = transformer.transform(content);
latestRootRef.current = root;
if (markmapRef.current) {
markmapRef.current.setData(root);
} else {
markmapRef.current = Markmap.create(svgRef.current, {
autoFit: false,
duration: 500,
}, root);
}
requestAnimationFrame(() => {
if (svgRef.current && hasRenderableSize(svgRef.current)) {
markmapRef.current?.fit();
}
});
} catch (error) {
console.error('Markmap error:', error);
} finally {
setLoading(false);
}
}, [content]);
useEffect(() => {
const svgElement = svgRef.current;
if (!svgElement || typeof ResizeObserver === 'undefined') {
return undefined;
}
const observer = new ResizeObserver(() => {
if (!hasRenderableSize(svgElement)) {
return;
}
if (!markmapRef.current && latestRootRef.current) {
markmapRef.current = Markmap.create(svgElement, {
autoFit: false,
duration: 500,
}, latestRootRef.current);
}
requestAnimationFrame(() => {
markmapRef.current?.fit();
});
});
observer.observe(svgElement);
return () => observer.disconnect();
}, []);
const handleFit = () => markmapRef.current?.fit();
const handleZoomIn = () => markmapRef.current?.rescale(1.2);
const handleZoomOut = () => markmapRef.current?.rescale(0.8);
if (!content) return