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 => {
 | 
			
		||||
    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 match = jsonString.match(fenceRegex);
 | 
			
		||||
    if (match && match[1]) {
 | 
			
		||||
        jsonString = match[1].trim();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const lastBracket = jsonString.lastIndexOf('}');
 | 
			
		||||
    if (lastBracket === -1) {
 | 
			
		||||
        throw new Error("No closing brace '}' found in API response.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let openBraceCount = 0;
 | 
			
		||||
    let firstBracket = -1;
 | 
			
		||||
    for (let i = lastBracket; i >= 0; i--) {
 | 
			
		||||
        if (jsonString[i] === '}') {
 | 
			
		||||
            openBraceCount++;
 | 
			
		||||
        } else if (jsonString[i] === '{') {
 | 
			
		||||
            openBraceCount--;
 | 
			
		||||
    // Try to parse directly first (most common case)
 | 
			
		||||
    try {
 | 
			
		||||
        return JSON.parse(jsonString);
 | 
			
		||||
    } catch (directParseError) {
 | 
			
		||||
        // If direct parsing fails, try the bracket extraction method
 | 
			
		||||
        const lastBracket = jsonString.lastIndexOf('}');
 | 
			
		||||
        if (lastBracket === -1) {
 | 
			
		||||
            throw new Error("No closing brace '}' found in API response.");
 | 
			
		||||
        }
 | 
			
		||||
        if (openBraceCount === 0) {
 | 
			
		||||
            firstBracket = i;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        let openBraceCount = 0;
 | 
			
		||||
        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(() => {
 | 
			
		||||
        if (value.trim()) {
 | 
			
		||||
            const renderDiagram = async () => {
 | 
			
		||||
                renderCountRef.current += 1;
 | 
			
		||||
                const uniqueId = `mermaid-diagram-${renderCountRef.current}`;
 | 
			
		||||
                
 | 
			
		||||
                try {
 | 
			
		||||
                    setError('');
 | 
			
		||||
                    renderCountRef.current += 1;
 | 
			
		||||
                    const uniqueId = `mermaid-diagram-${renderCountRef.current}`;
 | 
			
		||||
                    setDiagramHtml('');
 | 
			
		||||
                    const { svg } = await mermaid.render(uniqueId, value);
 | 
			
		||||
                    setDiagramHtml(svg);
 | 
			
		||||
                } catch (err) {
 | 
			
		||||
                    setError(err instanceof Error ? err.message : 'Mermaid diagram rendering failed');
 | 
			
		||||
                    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();
 | 
			
		||||
@ -201,6 +223,19 @@ const MermaidDiagram: React.FC<{ value: string; onChange: (value: string) => voi
 | 
			
		||||
            setError('');
 | 
			
		||||
        }
 | 
			
		||||
    }, [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 (
 | 
			
		||||
        <div className="space-y-3">
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@ import { Exercise } from '../types';
 | 
			
		||||
 | 
			
		||||
export const swaExercises7: Exercise[] = [
 | 
			
		||||
  {
 | 
			
		||||
    id: 'swa-6-1-ddd-tactical',
 | 
			
		||||
    id: 'swa-7-1-ddd-tactical',
 | 
			
		||||
    title: '6.1 - SWA - Domain-Driven Design (Tactical)',
 | 
			
		||||
    parts: [
 | 
			
		||||
      {
 | 
			
		||||
 | 
			
		||||
@ -512,9 +512,16 @@ CMD [ "serve", "-s", "dist", "-l", "8000" ]
 | 
			
		||||
- **Real-time Preview**: Users can see their Mermaid diagram rendered as they type
 | 
			
		||||
- **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
 | 
			
		||||
- **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
 | 
			
		||||
- **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.
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user