fixes and better mermaid
This commit is contained in:
		
							
								
								
									
										83
									
								
								App.tsx
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								App.tsx
									
									
									
									
									
								
							@ -27,37 +27,51 @@ import mermaid from 'mermaid';
 | 
				
			|||||||
const extractJson = (text: string): any => {
 | 
					const extractJson = (text: string): any => {
 | 
				
			||||||
    let jsonString = text.trim();
 | 
					    let jsonString = text.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove BOM and other invisible characters
 | 
				
			||||||
 | 
					    jsonString = jsonString.replace(/^\uFEFF/, ''); // Remove BOM
 | 
				
			||||||
 | 
					    jsonString = jsonString.replace(/^[\u200B-\u200D\uFEFF]/g, ''); // Remove zero-width characters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const fenceRegex = /^```(?:json)?\s*\n?(.*?)\n?\s*```$/s;
 | 
					    const fenceRegex = /^```(?:json)?\s*\n?(.*?)\n?\s*```$/s;
 | 
				
			||||||
    const match = jsonString.match(fenceRegex);
 | 
					    const match = jsonString.match(fenceRegex);
 | 
				
			||||||
    if (match && match[1]) {
 | 
					    if (match && match[1]) {
 | 
				
			||||||
        jsonString = match[1].trim();
 | 
					        jsonString = match[1].trim();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const lastBracket = jsonString.lastIndexOf('}');
 | 
					    // Try to parse directly first (most common case)
 | 
				
			||||||
    if (lastBracket === -1) {
 | 
					    try {
 | 
				
			||||||
        throw new Error("No closing brace '}' found in API response.");
 | 
					        return JSON.parse(jsonString);
 | 
				
			||||||
    }
 | 
					    } catch (directParseError) {
 | 
				
			||||||
 | 
					        // If direct parsing fails, try the bracket extraction method
 | 
				
			||||||
    let openBraceCount = 0;
 | 
					        const lastBracket = jsonString.lastIndexOf('}');
 | 
				
			||||||
    let firstBracket = -1;
 | 
					        if (lastBracket === -1) {
 | 
				
			||||||
    for (let i = lastBracket; i >= 0; i--) {
 | 
					            throw new Error("No closing brace '}' found in API response.");
 | 
				
			||||||
        if (jsonString[i] === '}') {
 | 
					 | 
				
			||||||
            openBraceCount++;
 | 
					 | 
				
			||||||
        } else if (jsonString[i] === '{') {
 | 
					 | 
				
			||||||
            openBraceCount--;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (openBraceCount === 0) {
 | 
					
 | 
				
			||||||
            firstBracket = i;
 | 
					        let openBraceCount = 0;
 | 
				
			||||||
            break;
 | 
					        let firstBracket = -1;
 | 
				
			||||||
 | 
					        for (let i = lastBracket; i >= 0; i--) {
 | 
				
			||||||
 | 
					            if (jsonString[i] === '}') {
 | 
				
			||||||
 | 
					                openBraceCount++;
 | 
				
			||||||
 | 
					            } else if (jsonString[i] === '{') {
 | 
				
			||||||
 | 
					                openBraceCount--;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (openBraceCount === 0) {
 | 
				
			||||||
 | 
					                firstBracket = i;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (firstBracket === -1) {
 | 
				
			||||||
 | 
					            throw new Error("Could not find matching opening brace '{' in API response.");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const finalJsonString = jsonString.substring(firstBracket, lastBracket + 1);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return JSON.parse(finalJsonString);
 | 
				
			||||||
 | 
					        } catch (finalParseError) {
 | 
				
			||||||
 | 
					            throw new Error(`JSON parsing failed: ${finalParseError.message}. Raw text: ${jsonString.substring(0, 100)}...`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (firstBracket === -1) {
 | 
					 | 
				
			||||||
        throw new Error("Could not find matching opening brace '{' in API response.");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const finalJsonString = jsonString.substring(firstBracket, lastBracket + 1);
 | 
					 | 
				
			||||||
    return JSON.parse(finalJsonString);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -184,15 +198,23 @@ const MermaidDiagram: React.FC<{ value: string; onChange: (value: string) => voi
 | 
				
			|||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        if (value.trim()) {
 | 
					        if (value.trim()) {
 | 
				
			||||||
            const renderDiagram = async () => {
 | 
					            const renderDiagram = async () => {
 | 
				
			||||||
 | 
					                renderCountRef.current += 1;
 | 
				
			||||||
 | 
					                const uniqueId = `mermaid-diagram-${renderCountRef.current}`;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
                try {
 | 
					                try {
 | 
				
			||||||
                    setError('');
 | 
					                    setError('');
 | 
				
			||||||
                    renderCountRef.current += 1;
 | 
					                    setDiagramHtml('');
 | 
				
			||||||
                    const uniqueId = `mermaid-diagram-${renderCountRef.current}`;
 | 
					 | 
				
			||||||
                    const { svg } = await mermaid.render(uniqueId, value);
 | 
					                    const { svg } = await mermaid.render(uniqueId, value);
 | 
				
			||||||
                    setDiagramHtml(svg);
 | 
					                    setDiagramHtml(svg);
 | 
				
			||||||
                } catch (err) {
 | 
					                } catch (err) {
 | 
				
			||||||
                    setError(err instanceof Error ? err.message : 'Mermaid diagram rendering failed');
 | 
					                    setError(err instanceof Error ? err.message : 'Mermaid diagram rendering failed');
 | 
				
			||||||
                    setDiagramHtml('');
 | 
					                    setDiagramHtml('');
 | 
				
			||||||
 | 
					                } finally {
 | 
				
			||||||
 | 
					                    // Clean up the DOM element that mermaid might have created
 | 
				
			||||||
 | 
					                    const element = document.getElementById(uniqueId);
 | 
				
			||||||
 | 
					                    if (element && element.parentNode) {
 | 
				
			||||||
 | 
					                        element.parentNode.removeChild(element);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
            renderDiagram();
 | 
					            renderDiagram();
 | 
				
			||||||
@ -202,6 +224,19 @@ const MermaidDiagram: React.FC<{ value: string; onChange: (value: string) => voi
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }, [value]);
 | 
					    }, [value]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Cleanup function to remove any lingering mermaid elements
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        return () => {
 | 
				
			||||||
 | 
					            // Clean up any mermaid elements that might be left in the DOM
 | 
				
			||||||
 | 
					            const mermaidElements = document.querySelectorAll('[id^="mermaid-diagram-"]');
 | 
				
			||||||
 | 
					            mermaidElements.forEach(element => {
 | 
				
			||||||
 | 
					                if (element.parentNode) {
 | 
				
			||||||
 | 
					                    element.parentNode.removeChild(element);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }, []);
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="space-y-3">
 | 
					        <div className="space-y-3">
 | 
				
			||||||
            <textarea
 | 
					            <textarea
 | 
				
			||||||
 | 
				
			|||||||
@ -3,7 +3,7 @@ import { Exercise } from '../types';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export const swaExercises7: Exercise[] = [
 | 
					export const swaExercises7: Exercise[] = [
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    id: 'swa-6-1-ddd-tactical',
 | 
					    id: 'swa-7-1-ddd-tactical',
 | 
				
			||||||
    title: '6.1 - SWA - Domain-Driven Design (Tactical)',
 | 
					    title: '6.1 - SWA - Domain-Driven Design (Tactical)',
 | 
				
			||||||
    parts: [
 | 
					    parts: [
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
 | 
				
			|||||||
@ -512,9 +512,16 @@ CMD [ "serve", "-s", "dist", "-l", "8000" ]
 | 
				
			|||||||
- **Real-time Preview**: Users can see their Mermaid diagram rendered as they type
 | 
					- **Real-time Preview**: Users can see their Mermaid diagram rendered as they type
 | 
				
			||||||
- **Error Handling**: Clear error messages for invalid Mermaid syntax
 | 
					- **Error Handling**: Clear error messages for invalid Mermaid syntax
 | 
				
			||||||
- **Unique Rendering**: Each render uses a unique ID to prevent caching issues that could cause diagrams to disappear
 | 
					- **Unique Rendering**: Each render uses a unique ID to prevent caching issues that could cause diagrams to disappear
 | 
				
			||||||
 | 
					- **DOM Cleanup**: Proper cleanup of mermaid-generated DOM elements to prevent accumulation of leftover elements from failed renders
 | 
				
			||||||
- **Consistent Styling**: Matches the existing design language of the application
 | 
					- **Consistent Styling**: Matches the existing design language of the application
 | 
				
			||||||
- **Accessibility**: Proper ARIA labels and semantic HTML structure
 | 
					- **Accessibility**: Proper ARIA labels and semantic HTML structure
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Important Implementation Details**:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **DOM Element Cleanup**: Added `finally` block in render function to clean up DOM elements that mermaid creates, especially important for failed renders which would otherwise leave orphaned elements
 | 
				
			||||||
 | 
					- **Component Unmount Cleanup**: Added cleanup `useEffect` to remove any lingering mermaid elements when component unmounts
 | 
				
			||||||
 | 
					- **Unique ID Generation**: Uses `useRef` counter to generate unique IDs for each render attempt, preventing mermaid's internal caching issues
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This enhancement makes the application more suitable for software architecture exercises that require diagram creation and visualization.
 | 
					This enhancement makes the application more suitable for software architecture exercises that require diagram creation and visualization.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
---
 | 
					---
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user