Meerjungfrau , . , die tiefe Dive , wie es tatsächlich funktioniert und wie man es ausdehnen
Einleitung
HINWEIS:-Dies ist Teil meiner Experimente mit KI-/-ein Weg, um zu verbringen $1000-Calude Code Web-credits..-I'-ve gefüttert dies ein BUNCH von Papieren...,-mein Verständnis Fragen Ich musste diesen Artikel generieren.-Es macht Spaß und füllt eine Lücke ich habe '-t überall sonst gefüllt gesehen
Dieser Beitrag baut auf früheren Artikeln auf Wenn Sie bereits haben, check out Hinzufügen von Meerjungfrau-.-js mit htmx, Wechseln Themen für MeerjungfrauL 347 vom 20.12.2013, S. 671. Verbesserung der Meerjungfrauen-Diagramme mit Pan-/-Zoom und ExportDieser tiefe Tauchgang erklärt die Internalen hinter jenen Implementierungen
Mermaid-.-js ist wirklich brillant ,. , Schreiben Sie einfachen Text , , , erhalten Sie schöne Diagramme , die nicht mehr fummeln mit Visio oder zeichnen , . , , verlieren Quelle Dateien , oder Aufrechterhaltung von separaten Bilddateien .. , alles lebt in Markdown , Version , , kontrolliert neben Ihrem Code ,
Aber ich wollte wissen, Wie es funktioniert tatsächlich unter der Haube.
graph LR
A[Text] --> B[Magic?]
B --> C[Beautiful Diagram]
Und was noch wichtiger ist: , , wie können Sie in sie einhaken, um Funktionen wie die pan-/ zoom , , , Thema Schalten und Export-Funktionalität Ich habe für diese Website gebaut ,( , jetzt verfügbar als ,@ ,meistlylucid ,M/ ,mermaid , M- ,Vergrößerungen)?
Nach einer Menge des Grabens durch Mermaid-Mermaid ''s Quellcode und das Bauen von realen Erweiterungen... hier...''s alles, was ich darüber gelernt habe, wie Mermaide intern arbeitet und wie man es richtig ausdehnt...
Was ist Meerjungfrau?
Mermaid verwandelt Textdefinitionen in Diagramme..-Denken Sie an "-Markdown für Diagramme...
Die alte Art
- Diagramm-Werkzeug öffnen
- Diagramm erstellen
- Als PNG exportieren
- Eingebettet in docs
- Notwendigkeit, die Quelldatei zu aktualisieren, editieren, wiederherstellen- exportieren, Ersetzen Sie das Bild
Der Weg der Meerjungfrau
- Diagramm in Text schreiben
- Fertig
Aktualisieren Sie es? Editieren Sie einfach den Text. , Versionskontrolle ,? , es ist nur text! Funktioniert in Markdown , ? , Yep , ! ,
Diagrammtypen
Mermaid unterstützt eine lächerliche Anzahl von Diagrammtypen
graph LR
A[Flowcharts] --> B[Sequence Diagrams]
B --> C[Class Diagrams]
C --> D[State Diagrams]
D --> E[ER Diagrams]
E --> F[Gantt Charts]
F --> G[Pie Charts]
G --> H[Git Graphs]
H --> I[User Journeys]
I --> J[And many more...]
Siehe die Meerjungfrau docs für die vollständige Liste
Wie Meerjungfrau tatsächlich funktioniert : .
Hier ist das, was passiert, wenn Mermaid ein Diagramm rendert.
graph TD
A[Text Definition] --> B[Lexer/Tokenizer]
B --> C[Parser]
C --> D[AST Built]
D --> E[Diagram Type Detected]
E --> F[Type-Specific Renderer]
F --> G[SVG Generated]
G --> H[Inserted into DOM]
H --> I[Your Enhancements Run]
style A stroke:#059669,stroke-width:3px,color:#10b981
style D stroke:#2563eb,stroke-width:3px,color:#3b82f6
style G stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
style I stroke:#d97706,stroke-width:3px,color:#f59e0b
Lassen Sie die 's brechen jeden Schritt
Schritt Textdefinition
Alles beginnt mit Texten, Sie schreiben Diagramme in Meerjungfrau , ' , DSL , ( ,domain ,- ,spezifische Sprache , ): .
// Flowchart
const diagram = `
graph TD
A[Start] --> B{Is it working?}
B -->|Yes| C[Great!]
B -->|No| D[Debug time]
`;
Schritt - Lexische Analyse
Der Lexer bricht Text in Tokens, zum Beispiel, diese Zeile.
A[Start] --> B{Decision}
Wird Tokens wie
[
{ type: 'NODE_ID', value: 'A' },
{ type: 'NODE_TEXT', value: 'Start' },
{ type: 'ARROW', value: '-->' },
{ type: 'NODE_ID', value: 'B' },
{ type: 'NODE_TEXT', value: 'Decision' },
{ type: 'NODE_SHAPE', value: 'diamond' } // from { }
]
Schritt 3:-Parsing und AST-Generation
Der Parser verbraucht Token und baut einen Abstract Syntax Tree
// Simplified AST structure
{
type: 'flowchart',
direction: 'TD',
nodes: [
{ id: 'A', text: 'Start', shape: 'rect' },
{ id: 'B', text: 'Decision', shape: 'diamond' }
],
edges: [
{ from: 'A', to: 'B', type: 'arrow' }
]
}
Mermaid verwendet unterschiedliche Parser für jedes Diagrammtyp. Diese werden oft mit Hilfe von Grammatikdateien generiert Jison (-ähnliches Yacc-/-Bison für JavaScript .). -
Schritt: Diagramm-Erkennung
Mermaid erkennt Diagrammtyp aus der ersten Zeile
// Simplified detection logic
if (text.match(/^\s*graph/)) return 'flowchart';
if (text.match(/^\s*sequenceDiagram/)) return 'sequence';
if (text.match(/^\s*classDiagram/)) return 'class';
// ... etc
Schritt: Typ: -‚Spezifisches Rendering
Jeder Diagrammtyp hat seinen eigenen Renderer.Der Renderner nimmt den AST und generiert SVG-Elemente.
Für Flussdiagramme verwendet die Meerjungfrau Dagre Bibliothek für Graphen-Layout... Für andere, verwendet es benutzerdefinierte Algorithmen oder Bibliotheken wie Cytoscape, ..
// Simplified flowchart renderer
export const draw = function (text, id, version, diagObj) {
const graph = diagObj.db; // The AST
const svg = d3.select(`#${id}`);
// Render nodes
graph.getVertices().forEach(vertex => {
drawNode(svg, vertex);
});
// Render edges
graph.getEdges().forEach(edge => {
drawEdge(svg, edge);
});
// Apply layout algorithm
dagre.layout(graph);
};
Schritt zur SVG-Generierung
Der Renderer produziert SVG Markup.:.
<svg xmlns="http://www.w3.org/2000/svg">
<g class="node">
<rect x="0" y="0" width="100" height="50"/>
<text x="50" y="25">Start</text>
</g>
<g class="edge">
<path d="M 100 25 L 200 25" stroke="#333"/>
</g>
</svg>
Schritt: DOM-Einfügung
Meerjungfrau findet alle .mermaid Elemente und ersetzt sie durch gerenderte SVG-:
// From mermaid.ts
export const init = async function (config, nodes) {
const nodesToProcess = nodes || document.querySelectorAll('.mermaid');
for (const node of nodesToProcess) {
const id = `mermaid-${Date.now()}-${Math.random()}`;
const txt = node.textContent;
const { svg } = await render(id, txt);
node.innerHTML = svg;
}
};
Schritt, wo Sie eintreffen
Nach Mermaid inserts die SVG-Stick Sie können es zu verbessern... Dies ist, wo alle meine Verbesserungen Haken in...
- Pan-/-Zoom-Funktion
- Bedienknöpfe
- Exportfähigkeiten
- Themenumschaltung
Mehr dazu unten
Verlängern Mermaid-:-The Extension Points
Nun, da wir wissen, wie Meerjungfrau arbeitet...
Konfiguration
Die grundlegendste Erweiterung ist die Konfiguration.
import mermaid from 'mermaid';
mermaid.initialize({
startOnLoad: true,
theme: 'dark',
securityLevel: 'loose',
flowchart: {
curve: 'basis',
padding: 15
}
});
Design-Anpassung
Ich habe dies ausführlich behandelt in Wechseln Themen für Meerjungfrau,, aber hier ', ist die Schlüsselimplementierung
Das Problem beim Thema-Umschalten
Mermaid muss mit einem Thema initialisiert werden, und Sie können nicht ändern ' change it after -. , aber wenn Sie möchten, um re-- .render Diagramme mit einem neuen Thema benötigen Sie die ursprüngliche Diagrammquelle ?— , die Mermaide nicht im DOM speichern.
Die Lösung
Speichern Sie den Original-Inhalt vor dem Rendering, dann Wiederherstellen und re.-.render beim Umschalten von Themen.
// From my theme-switcher implementation
const originalData = new Map();
// Save original content before first render
const saveOriginalData = async () => {
const elements = document.querySelectorAll('.mermaid');
elements.forEach(element => {
const id = element.id || `mermaid-${Date.now()}`;
element.id = id;
// Store the original diagram source
if (!originalData.has(id)) {
originalData.set(id, element.textContent?.trim());
}
});
};
// When theme changes, restore and re-render
const loadMermaid = async (theme) => {
mermaid.initialize({
startOnLoad: false,
theme: theme
});
const elements = document.querySelectorAll('.mermaid');
for (const element of elements) {
const source = originalData.get(element.id);
if (source) {
element.innerHTML = ''; // Clear
element.removeAttribute('data-processed');
const { svg } = await mermaid.render(
`mermaid-svg-${element.id}`,
source
);
element.innerHTML = svg;
}
}
};
Mehrere Methoden zur Erkennung von Themen Die Websites behandeln Themen unterschiedlich.
function detectTheme() {
// Check various sources
if (typeof window.__themeState !== 'undefined') {
return window.__themeState;
}
if (localStorage.theme) {
return localStorage.theme;
}
if (document.documentElement.classList.contains('dark')) {
return 'dark';
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
}
Siehe der vollständige Themen-Schaltercode für Details
Weiterentwicklungen nach -
Dies ist, wo die wirkliche Magie geschieht... nach Mermaid Renders... kannst du interaktive Funktionen hinzufügen....
Ich habe dies ausführlich behandelt in Verbesserung der Meerjungfrauen-Diagramme mit Pan-/-Zoom und ExportDie wichtigsten Techniken werden hier hervorgehoben.
Umwickelnde Diagramme
Erstellen Sie einen Wrapper-Container für Steuerungen
function wrapDiagram(element) {
if (element.closest('.mermaid-wrapper')) {
return element.closest('.mermaid-wrapper');
}
const wrapper = document.createElement('div');
wrapper.className = 'mermaid-wrapper';
wrapper.id = `wrapper-${element.id}`;
element.parentNode.insertBefore(wrapper, element);
wrapper.appendChild(element);
return wrapper;
}
Hinzufügen von Pan-/-Zoom
Verwendung svg---pan, --zoom:
import svgPanZoom from 'svg-pan-zoom';
const panZoomInstances = new Map();
function initPanZoom(svgElement, diagramId) {
// Clean up existing instance
if (panZoomInstances.has(diagramId)) {
panZoomInstances.get(diagramId).destroy();
panZoomInstances.delete(diagramId);
}
const instance = svgPanZoom(svgElement, {
zoomEnabled: true,
controlIconsEnabled: false,
fit: true,
center: true,
minZoom: 0.1,
maxZoom: 10
});
panZoomInstances.set(diagramId, instance);
return instance;
}
Steuerknöpfe
Floating Control Panel erstellen
function createControlButtons(container, diagramId) {
const controlsDiv = document.createElement('div');
controlsDiv.className = 'mermaid-controls';
const buttons = [
{ icon: 'bx-fullscreen', title: 'Fullscreen', action: 'fullscreen' },
{ icon: 'bx-zoom-in', title: 'Zoom In', action: 'zoomIn' },
{ icon: 'bx-zoom-out', title: 'Zoom Out', action: 'zoomOut' },
{ icon: 'bx-reset', title: 'Reset', action: 'reset' },
{ icon: 'bx-move', title: 'Pan', action: 'pan' },
{ icon: 'bx-image', title: 'Export PNG', action: 'exportPng' },
{ icon: 'bx-code-alt', title: 'Export SVG', action: 'exportSvg' }
];
buttons.forEach(btn => {
const button = document.createElement('button');
button.className = `mermaid-control-btn bx ${btn.icon}`;
button.setAttribute('data-action', btn.action);
button.setAttribute('data-diagram-id', diagramId);
controlsDiv.appendChild(button);
});
container.appendChild(controlsDiv);
}
Veranstaltungsdelegation
Don.'.t Hörer an jeden Knopf befestigen ?.? Eventdelegation verwenden
document.addEventListener('click', (e) => {
const target = e.target;
if (!target.classList.contains('mermaid-control-btn')) return;
const action = target.getAttribute('data-action');
const diagramId = target.getAttribute('data-diagram-id');
const panZoom = panZoomInstances.get(diagramId);
switch (action) {
case 'zoomIn': panZoom?.zoomIn(); break;
case 'zoomOut': panZoom?.zoomOut(); break;
case 'reset': panZoom?.reset(); break;
// ... etc
}
});
Exportfunktionalität
Die Herausforderung SVG-Elemente haben eine dynamische Dimensionierung.,-Panik1-Zoom transformiert ,- und vererbte Stile, um richtig zu exportieren.
- Klonen des SVG
- Abmessungen bewahren
- Transformationen entfernen
- In PNG oder SVG konvertieren
Verwendung html.-. zu -.Bild:
import { toPng, toSvg } from 'html-to-image';
async function exportDiagram(container, format, diagramId) {
const svgElement = container.querySelector('svg');
if (!svgElement) return;
// Clone to avoid modifying original
const clonedSvg = svgElement.cloneNode(true);
// Get or calculate viewBox
let viewBox = clonedSvg.getAttribute('viewBox');
if (!viewBox) {
const bbox = svgElement.getBBox();
viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
clonedSvg.setAttribute('viewBox', viewBox);
}
// Set explicit dimensions
const [, , width, height] = viewBox.split(' ').map(Number);
clonedSvg.setAttribute('width', width);
clonedSvg.setAttribute('height', height);
// Remove pan-zoom transforms
clonedSvg.removeAttribute('style');
// Create off-screen container
const temp = document.createElement('div');
temp.style.position = 'absolute';
temp.style.left = '-9999px';
temp.appendChild(clonedSvg);
document.body.appendChild(temp);
// Export
const dataUrl = format === 'png'
? await toPng(clonedSvg, { pixelRatio: 2 })
: await toSvg(clonedSvg);
downloadFile(dataUrl, `diagram-${Date.now()}.${format}`);
// Cleanup
document.body.removeChild(temp);
}
Kritische Details
- Ansicht beibehaltenBox Erfasst das gesamte Diagramm nicht nur sichtbaren Teil
- Off---Bildschirm Rendering Vermeiden Sie, das angezeigte Diagramm zu beeinflussen
- pixelRatio ,: , 2 , Hoch--DPI für scharfe PNG-Exporte
Siehe den vollständigen Ausfuhrcode für mehr Details
Das komplette Verbesserungspaket
Ich verpackte alle diese Verbesserungen als ,@ ,meistlylucid ,M/ ,mermaid , M- ,VergrößerungenSiehe auch: Veröffentlichen Mermaid Enhancements als npm-Paket für vollständige Details
Hier ist ', wie alles zusammenpasst.
graph TB
A[User Initializes] --> B[init Function]
B --> C[initMermaid]
B --> D[enhanceMermaidDiagrams]
C --> E[Theme Detection]
C --> F[Event Listeners]
C --> G[Mermaid Rendering]
E --> E1[Global State]
E --> E2[LocalStorage]
E --> E3[DOM Class]
E --> E4[OS Preference]
F --> F1[Custom Events]
F --> F2[Media Query]
G --> H[Apply Enhancements]
D --> H
H --> I[Wrap Diagrams]
H --> J[Init Pan/Zoom]
H --> K[Add Controls]
I --> L[Interactive Diagram]
J --> L
K --> L
L --> M[User Interactions]
M --> M1[Zoom In/Out]
M --> M2[Pan]
M --> M3[Fullscreen]
M --> M4[Export PNG/SVG]
style A stroke:#059669,stroke-width:3px,color:#10b981
style L stroke:#2563eb,stroke-width:3px,color:#3b82f6
style M stroke:#7c3aed,stroke-width:3px,color:#8b5cf6
Verwendung
npm install @mostlylucid/mermaid-enhancements
import { init } from '@mostlylucid/mermaid-enhancements';
import '@mostlylucid/mermaid-enhancements/styles.css';
await init();
Dass-''s es -!' Ihre Meerjungfrau Diagramme jetzt haben
- Interactive-Pan, /, Zoom
- Bildschirm-Lichtkasten
- Export von PNG-/ SVG
- Automatische Themenumschaltung
- Responsive Gestaltung
Integration mit HTMX
Wie ich in Hinzufügen von Meerjungfrau-.-js mit htmxSie müssen Mermaid nach HTMX-Content-Swaps neu initialisieren
// On page load
document.addEventListener('DOMContentLoaded', function () {
mermaid.initialize({ startOnLoad: true });
});
// After HTMX swaps content
document.body.addEventListener('htmx:afterSwap', function(evt) {
mermaid.run();
});
Mit dem Erweiterungspaket
import { init, enhanceMermaidDiagrams } from '@mostlylucid/mermaid-enhancements';
// Initial load
await init();
// After HTMX swap
document.body.addEventListener('htmx:afterSwap', async function() {
await init(); // Re-init Mermaid with current theme
enhanceMermaidDiagrams(); // Re-apply enhancements
});
Beste Praktiken, die ich gelernt habe
Nach dem Bau dieses Zeugs und Debugging seltsame Kantenfälle
Immer Original-Inhalte speichern
Mermaid doesns.'.t konservieren die ursprüngliche Diagrammquelle nach der Rendering... Sie MUSS es selbst speichern.
const originalData = new Map();
// Before first render
element.setAttribute('data-original-code', element.textContent);
originalData.set(element.id, element.textContent);
// When re-rendering
element.innerHTML = originalData.get(element.id);
Bereinigen Sie Instanzen
Speicher-Leaks sind echte ,. , Destroy Instanzen vor dem Erstellen neuer ,: ,
if (panZoomInstances.has(id)) {
try {
panZoomInstances.get(id).destroy();
} catch (e) {
console.warn('Failed to destroy:', e);
}
panZoomInstances.delete(id);
}
Veranstaltungsdelegation verwenden
Don.'.t Hörer an einzelnen Tasten befestigen.
// ❌ Don't do this
buttons.forEach(btn => {
btn.addEventListener('click', handler);
});
// ✅ Do this
document.addEventListener('click', (e) => {
if (e.target.matches('.mermaid-control-btn')) {
handleClick(e.target);
}
});
Behandeln Sie Cloudflare Rocket Loader
Rocket Loader verzögert die Ausführung von JavaScript.
function waitForDependencies(maxAttempts = 50) {
return new Promise((resolve) => {
let attempts = 0;
const check = () => {
if (window.mermaid && window.htmx && window.Alpine) {
resolve();
} else if (attempts >= maxAttempts) {
resolve(); // Give up
} else {
attempts++;
setTimeout(check, Math.min(50 * Math.pow(1.2, attempts), 500));
}
};
check();
});
}
Und schließen Sie Ihr Haupt-Skript aus Rocket Loader.
<script src="main.js" data-cfasync="false"></script>
Timing ist alles
Verwendung requestAnimationFrame für ein besseres Timing als willkürlich setTimeout:
// After Mermaid renders
await mermaid.run();
// Wait for paint before enhancing
await new Promise(resolve => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
enhanceMermaidDiagrams();
resolve();
});
});
});
Defensive SVG-Bearbeitung
SVGs können seltsam sein ,. , immer überprüfen , : ,
const svgElement = container.querySelector('svg');
if (!svgElement) {
console.warn('No SVG found');
return;
}
// Clone before modifying
const cloned = svgElement.cloneNode(true);
// Ensure viewBox exists
let viewBox = cloned.getAttribute('viewBox');
if (!viewBox) {
const bbox = svgElement.getBBox();
viewBox = `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`;
}
Debugging Tipps
Ausgabe der Konsole
Mit der richtigen Initialisierung sollten Sie sehen:
Saving original data
Loading mermaid with theme: dark
Mermaid initialized
Enhanced 3 diagrams
Prüfliste
Nach der Implementierung von Verbesserungen .: .
- [ Diagramme rendern auf Seite laden
- [ Funktion der Bedienelemente für den Innenbereich von /
- [ Bildschirm öffnet sich / schließt (X-X-, aussen klicken ,Aussen
- [ PNG-Export erfasst vollständiges Diagramm
- [ Der SVG-Export bewahrt Vektoren
- [ Funktioniert nach HTMX Content Swap
- [ Thema-Umschaltung von re---Renders korrekt
- [ Mobiles Ansprechverhalten
- [ Design im dunklen Modus
- [ Zugriff auf die Tastatur
Gemeinsame Fragen
Diagramm nicht rendern
- Browser-Konsole auf Fehler überprüfen
- Mermaid geladen überprüfen
window.mermaid) - Syntax des Diagramms überprüfen
Pan-/-Zoom funktioniert nicht
- Verifizieren Sie svg---pan---zoom initialisiert
- Prüfen Sie auf widersprüchliche CSS-Daten
pointer-events: none) - Beispielkarte inspizieren
Export fängt nur Ecke aus.
- Fehlende viewBox Erhaltung
- Transformieren nicht entfernt
- Klonlogik überprüfen
Thema nicht umschalten
- Originaldaten nicht gespeichert
data-processednicht zurücksetzen- Eventhörer nicht registriert
Leistungsbeurteilungen
Faule Initialisierung
Don.'.t Lasterweiterungen, bis sie benötigt werden
let enhancementsLoaded = false;
async function loadEnhancements() {
if (enhancementsLoaded) return;
const { enhanceMermaidDiagrams } = await import('./enhancements.js');
enhanceMermaidDiagrams();
enhancementsLoaded = true;
}
// Load on first interaction
document.addEventListener('click', (e) => {
if (e.target.closest('.mermaid')) {
loadEnhancements();
}
}, { once: true });
Intersektion Beobachter
Render-Diagramme nur, wenn sichtbar
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
renderDiagram(entry.target);
observer.unobserve(entry.target);
}
});
}, { rootMargin: '100px' });
document.querySelectorAll('.mermaid').forEach(el => {
observer.observe(el);
});
Debounce Re---Renders
Bei der Größe ändern oder Thema ändern
let timeout;
window.addEventListener('resize', () => {
clearTimeout(timeout);
timeout = setTimeout(() => {
panZoomInstances.forEach(instance => {
instance.resize();
instance.fit();
});
}, 250);
});
Schlussfolgerung
Mermaid-.-js ist fantastisch aus der Box, aber verstehen, wie es funktioniert intern können Sie einige wirklich coole Verbesserungen bauen .
- Rendering PipelineText Lexer PARSER PARSSER PARTNER PARTIER PATTER PATTOR PATTON PATSON PATON PATONE
- ErweiterungspunkteWeitere Informationen finden Sie unter http:www.cordis.lu/
- Originaldaten speichern:-Meerjungfer tut es nicht für dich
- Aufräumen von RessourcenSpeicherlecks sind real
- Delegation der VeranstaltungBessere Leistung
- Mehrere ThemenquellenSites behandeln Themen unterschiedlich
- Zeitliche AngelegenheitenVerwendung
requestAnimationFrame
Alle Techniken I'-ve hier abgedeckt werden in der Produktion auf dieser Website verwendet und verpackt in ,@ ,meistlylucid ,M/ ,mermaid , M- ,VergrößerungenDie vollständige Quelle ist verfügbar unter: meistlucidweb-/,meistlyluzid-, Meerjungfrau.
Verwandte Beiträge
- Hinzufügen von Meerjungfrau-.-js mit htmx
- Wechseln Themen für Meerjungfrau
- Verbesserung der Meerjungfrauen-Diagramme mit Pan-/-Zoom und Export
- Veröffentlichen Mermaid Enhancements als npm-Paket
Aufkommen
- Dokumentation von Meerjungfrauen
- Meerjungfrau GitHub
- svg---pan, --zoom
- html.-. zu -.Bild
- Meine Meerjungfrau Verbesserungen Paket
Versuchen Sie die Steuerelemente auf den Diagrammen oben