feat: add mermaid diagram support

This commit is contained in:
2025-07-09 13:59:21 +02:00
parent 9ed3520f1f
commit 43a7647a9c
4 changed files with 1215 additions and 2 deletions

73
App.tsx
View File

@ -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>