feat: add mermaid diagram support
This commit is contained in:
73
App.tsx
73
App.tsx
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import { Cog6ToothIcon } from "@heroicons/react/24/outline";
|
||||
import { Exercise, CheckResult, ExercisePartType } from './types';
|
||||
@ -18,6 +18,7 @@ import Editor from 'react-simple-code-editor';
|
||||
import Prism from 'prismjs/components/prism-core';
|
||||
import 'prismjs/components/prism-clike';
|
||||
import 'prismjs/components/prism-c';
|
||||
import mermaid from 'mermaid';
|
||||
|
||||
|
||||
// --- GEMINI API SETUP & HELPERS ---
|
||||
@ -167,6 +168,68 @@ const TextAreaEditor: React.FC<{
|
||||
)
|
||||
};
|
||||
|
||||
const MermaidDiagram: React.FC<{ value: string; onChange: (value: string) => void; placeholder?: string }> = ({ value, onChange, placeholder }) => {
|
||||
const [diagramHtml, setDiagramHtml] = useState<string>('');
|
||||
const [error, setError] = useState<string>('');
|
||||
const renderCountRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
theme: 'default',
|
||||
securityLevel: 'loose'
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (value.trim()) {
|
||||
const renderDiagram = async () => {
|
||||
try {
|
||||
setError('');
|
||||
renderCountRef.current += 1;
|
||||
const uniqueId = `mermaid-diagram-${renderCountRef.current}`;
|
||||
const { svg } = await mermaid.render(uniqueId, value);
|
||||
setDiagramHtml(svg);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Mermaid diagram rendering failed');
|
||||
setDiagramHtml('');
|
||||
}
|
||||
};
|
||||
renderDiagram();
|
||||
} else {
|
||||
setDiagramHtml('');
|
||||
setError('');
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className="w-full h-48 p-3 bg-slate-50 border-2 border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 font-mono text-slate-800"
|
||||
aria-label="Mermaid diagram input"
|
||||
/>
|
||||
{error && (
|
||||
<div className="p-3 bg-red-50 border-2 border-red-200 rounded-lg">
|
||||
<p className="text-red-700 text-sm font-medium">Mermaid Error:</p>
|
||||
<p className="text-red-600 text-sm">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
{diagramHtml && (
|
||||
<div className="p-4 bg-white border-2 border-slate-200 rounded-lg">
|
||||
<p className="text-slate-600 text-sm mb-2 font-medium">Preview:</p>
|
||||
<div
|
||||
className="mermaid-preview"
|
||||
dangerouslySetInnerHTML={{ __html: diagramHtml }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
interface CodeDiffViewerProps {
|
||||
oldCode: string;
|
||||
@ -320,11 +383,17 @@ const ExerciseSheet: React.FC<ExerciseSheetProps> = ({ exercise, onNext, ai }) =
|
||||
onChange={(value) => handleInputChange(part.id, value)}
|
||||
placeholder={`// Code für Teil ${part.id}) hier eingeben...`}
|
||||
/>
|
||||
) : part.type === 'mermaid' ? (
|
||||
<MermaidDiagram
|
||||
value={inputs[part.id] || ''}
|
||||
onChange={(value) => handleInputChange(part.id, value)}
|
||||
placeholder="Mermaid-Diagramm hier eingeben..."
|
||||
/>
|
||||
) : (
|
||||
<TextAreaEditor
|
||||
value={inputs[part.id] || ''}
|
||||
onChange={(value) => handleInputChange(part.id, value)}
|
||||
placeholder={part.type === 'mermaid' ? 'Antwort in Mermaid-Syntax hier eingeben...' : `Antwort hier eingeben...`}
|
||||
placeholder={`Antwort hier eingeben...`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -375,4 +375,146 @@ CMD [ "serve", "-s", "dist", "-l", "8000" ]
|
||||
+GEMINI_API_KEY=
|
||||
```
|
||||
|
||||
### 5. Optional: Adding Mermaid Diagram Support
|
||||
|
||||
**Objective**: To provide interactive Mermaid diagram rendering for exercises that require diagram creation, enhancing the user experience for architecture-related questions.
|
||||
|
||||
**Previous State**: The application had 'mermaid' as a defined exercise type in `types.ts`, but no actual Mermaid rendering component was implemented.
|
||||
|
||||
**Changes Made**:
|
||||
|
||||
- **Mermaid Dependency**: Added `mermaid` package (version 11.8.1) to handle diagram rendering.
|
||||
- **MermaidDiagram Component**: Created a new React component that:
|
||||
- Provides a textarea for Mermaid syntax input
|
||||
- Renders diagrams in real-time as the user types
|
||||
- Shows error messages for invalid syntax
|
||||
- Displays a preview of the rendered diagram
|
||||
- Uses unique IDs for each render to prevent caching issues
|
||||
- **Exercise Integration**: Modified the `ExerciseSheet` component to use `MermaidDiagram` for exercises with `type: 'mermaid'`
|
||||
|
||||
**Step-by-step Actions**:
|
||||
|
||||
1. Add `mermaid` import to `App.tsx`
|
||||
2. Create the `MermaidDiagram` component with state management for diagram HTML and errors
|
||||
3. Implement `useEffect` hooks to initialize Mermaid and handle diagram rendering
|
||||
4. Add unique ID generation using `useRef` to prevent render caching issues
|
||||
5. Update the exercise rendering logic to conditionally show `MermaidDiagram` for mermaid exercises
|
||||
6. Add appropriate styling and error handling for the diagram preview
|
||||
|
||||
**Code Changes (App.tsx)**:
|
||||
|
||||
```diff
|
||||
--- a/App.tsx
|
||||
+++ b/App.tsx
|
||||
@@ -1,4 +1,4 @@
|
||||
-import React, { useState, useEffect, useCallback, useMemo } from 'react';
|
||||
+import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
||||
import { Dialog } from "@headlessui/react";
|
||||
import { Cog6ToothIcon } from "@heroicons/react/24/outline";
|
||||
import { Exercise, CheckResult, ExercisePartType } from './types';
|
||||
@@ -19,6 +19,7 @@ import Editor from 'react-simple-code-editor';
|
||||
import Prism from 'prismjs/components/prism-core';
|
||||
import 'prismjs/components/prism-clike';
|
||||
import 'prismjs/components/prism-c';
|
||||
+import mermaid from 'mermaid';
|
||||
|
||||
|
||||
// --- GEMINI API SETUP & HELPERS ---
|
||||
@@ -168,6 +169,47 @@ const TextAreaEditor: React.FC<{
|
||||
)
|
||||
};
|
||||
|
||||
+const MermaidDiagram: React.FC<{ value: string; onChange: (value: string) => void; placeholder?: string }> = ({ value, onChange, placeholder }) => {
|
||||
+ const [diagramHtml, setDiagramHtml] = useState<string>('');
|
||||
+ const [error, setError] = useState<string>('');
|
||||
+ const renderCountRef = useRef(0);
|
||||
+
|
||||
+ useEffect(() => {
|
||||
+ mermaid.initialize({
|
||||
+ startOnLoad: false,
|
||||
+ theme: 'default',
|
||||
+ securityLevel: 'loose'
|
||||
+ });
|
||||
+ }, []);
|
||||
+
|
||||
+ useEffect(() => {
|
||||
+ if (value.trim()) {
|
||||
+ const renderDiagram = async () => {
|
||||
+ try {
|
||||
+ setError('');
|
||||
+ renderCountRef.current += 1;
|
||||
+ const uniqueId = `mermaid-diagram-${renderCountRef.current}`;
|
||||
+ const { svg } = await mermaid.render(uniqueId, value);
|
||||
+ setDiagramHtml(svg);
|
||||
+ } catch (err) {
|
||||
+ setError(err instanceof Error ? err.message : 'Mermaid diagram rendering failed');
|
||||
+ setDiagramHtml('');
|
||||
+ }
|
||||
+ };
|
||||
+ renderDiagram();
|
||||
+ } else {
|
||||
+ setDiagramHtml('');
|
||||
+ setError('');
|
||||
+ }
|
||||
+ }, [value]);
|
||||
+
|
||||
+ return (
|
||||
+ <div className="space-y-3">
|
||||
+ <textarea
|
||||
+ value={value}
|
||||
+ onChange={(e) => onChange(e.target.value)}
|
||||
+ placeholder={placeholder}
|
||||
+ className="w-full h-48 p-3 bg-slate-50 border-2 border-slate-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all duration-200 font-mono text-slate-800"
|
||||
+ aria-label="Mermaid diagram input"
|
||||
+ />
|
||||
+ {error && (
|
||||
+ <div className="p-3 bg-red-50 border-2 border-red-200 rounded-lg">
|
||||
+ <p className="text-red-700 text-sm font-medium">Mermaid Error:</p>
|
||||
+ <p className="text-red-600 text-sm">{error}</p>
|
||||
+ </div>
|
||||
+ )}
|
||||
+ {diagramHtml && (
|
||||
+ <div className="p-4 bg-white border-2 border-slate-200 rounded-lg">
|
||||
+ <p className="text-slate-600 text-sm mb-2 font-medium">Preview:</p>
|
||||
+ <div
|
||||
+ className="mermaid-preview"
|
||||
+ dangerouslySetInnerHTML={{ __html: diagramHtml }}
|
||||
+ />
|
||||
+ </div>
|
||||
+ )}
|
||||
+ </div>
|
||||
+ );
|
||||
+};
|
||||
|
||||
@@ -376,12 +418,17 @@ const ExerciseSheet: React.FC<ExerciseSheetProps> = ({ exercise, onNext, ai })
|
||||
placeholder={`// Code für Teil ${part.id}) hier eingeben...`}
|
||||
/>
|
||||
- ) : (
|
||||
+ ) : part.type === 'mermaid' ? (
|
||||
+ <MermaidDiagram
|
||||
+ value={inputs[part.id] || ''}
|
||||
+ onChange={(value) => handleInputChange(part.id, value)}
|
||||
+ placeholder="Mermaid-Diagramm hier eingeben..."
|
||||
+ />
|
||||
+ ) : (
|
||||
<TextAreaEditor
|
||||
value={inputs[part.id] || ''}
|
||||
onChange={(value) => handleInputChange(part.id, value)}
|
||||
- placeholder={part.type === 'mermaid' ? 'Antwort in Mermaid-Syntax hier eingeben...' : `Antwort hier eingeben...`}
|
||||
+ placeholder={`Antwort hier eingeben...`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
```
|
||||
|
||||
**Key Features**:
|
||||
|
||||
- **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
|
||||
- **Consistent Styling**: Matches the existing design language of the application
|
||||
- **Accessibility**: Proper ARIA labels and semantic HTML structure
|
||||
|
||||
This enhancement makes the application more suitable for software architecture exercises that require diagram creation and visualization.
|
||||
|
||||
---
|
||||
|
@ -13,6 +13,7 @@
|
||||
"@headlessui/react": "^2.2.4",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"diff": "5.2.0",
|
||||
"mermaid": "^11.8.1",
|
||||
"prismjs": "1.29.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
|
1001
pnpm-lock.yaml
generated
1001
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user