diff --git a/frontend/src/components/MermaidRenderer.tsx b/frontend/src/components/MermaidRenderer.tsx index b9014581..11285bc5 100644 --- a/frontend/src/components/MermaidRenderer.tsx +++ b/frontend/src/components/MermaidRenderer.tsx @@ -22,6 +22,7 @@ const MermaidRenderer: React.FC = ({ const containerRef = useRef(null); const [hoverPosition, setHoverPosition] = useState<{ x: number, y: number } | null>(null); const [isHovering, setIsHovering] = useState(false); + const [zoomFactor, setZoomFactor] = useState(2); const handleMouseMove = (event: React.MouseEvent) => { if (!containerRef.current) return; @@ -39,55 +40,70 @@ const MermaidRenderer: React.FC = ({ setHoverPosition(null); }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (!isHovering) return; + + if (event.key === '+' || event.key === '=') { + setZoomFactor(prev => Math.min(6, prev + 0.5)); // Cap at 6x + event.preventDefault(); + } + else if (event.key === '-') { + setZoomFactor(prev => Math.max(1, prev - 0.5)); // Minimum 1x + event.preventDefault(); + } + }; + + const handleWheel = (event: React.WheelEvent) => { + if (!isHovering) return; + + if ( event.ctrlKey || event.metaKey) { + event.preventDefault(); + + if (event.deltaY < 0) { + setZoomFactor(prev => Math.min(6, prev + 0.25)); + } else { + setZoomFactor(prev => Math.max(1, prev - 0.25)); + } + } + }; + const getTransformOrigin = () => { if (!hoverPosition) return 'center center'; return `${hoverPosition.x * 100}% ${hoverPosition.y * 100}%`; }; - useEffect(() => { - if ((isLoading !== undefined ? isLoading : status === 'loading') || !code) return; + useEffect(() => { + const renderDiagram = async () => { mermaid.initialize({ startOnLoad: true, theme: isDarkTheme ? 'dark' : 'default', securityLevel: 'loose', suppressErrorRendering: true, }); - - const renderDiagram = async (): Promise => { + + const isCurrentlyLoading = isLoading !== undefined ? isLoading : status === 'loading'; + if (!isCurrentlyLoading && code) { try { - await mermaid.parse(code); //throws syntax errors - const element = document.getElementById(diagramId.current); if (element) { element.removeAttribute('data-processed'); + await mermaid.parse(code); //syntax check mermaid.contentLoaded(); - - const svgElement = element.querySelector('svg'); - if (svgElement) { - svgElement.setAttribute('width', '100%'); - svgElement.setAttribute('height', 'auto'); - svgElement.style.maxWidth = '100%'; - svgElement.style.width = '100%'; - - svgElement.removeAttribute('viewBox'); - - } - setError(null); } } catch (err) { - - setError( - `Failed to render Mermaid diagram: ${err instanceof Error ? err.message : String(err)}` - ); + console.error('Error rendering mermaid diagram:', err); + setError(`Failed to render diagram: ${err instanceof Error ? err.message : String(err)}`); } - }; - - renderDiagram(); - + } + }; + renderDiagram(); }, [code, isDarkTheme, isLoading]); + + useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( @@ -238,8 +254,6 @@ const MermaidRenderer: React.FC = ({ const showDiagramOptions = !isCurrentlyLoading && !error; const errorRender = !isCurrentlyLoading && error; - - return (
@@ -302,7 +316,7 @@ const MermaidRenderer: React.FC = ({ Loading diagram...
- ) : errorRender ? ( + ) : errorRender ? (
{error} @@ -312,7 +326,7 @@ const MermaidRenderer: React.FC = ({ <>
= ({ onMouseMove={handleMouseMove} onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} + onKeyDown={handleKeyDown} + onWheel={handleWheel} + tabIndex={0} + > + {isHovering && ( + <> +
+ + { + setZoomFactor(2); + }} + title="Reset zoom" + > + {zoomFactor.toFixed(1)}x + + +
+ + )}