/**
* Mindmap.js - Modul für die Mindmap-Visualisierung mit Cytoscape.js
* Version: 1.0.0
* Datum: 01.05.2025
*/
// Stellen Sie sicher, dass das globale MindMap-Objekt existiert
if (!window.MindMap) {
window.MindMap = {};
}
/**
* Mindmap-Visualisierungsklasse
*/
class MindmapVisualization {
/**
* Konstruktor für die Mindmap-Visualisierung
* @param {string} containerId - ID des Container-Elements
* @param {Object} options - Konfigurationsoptionen
*/
constructor(containerId, options = {}) {
this.containerId = containerId;
this.container = document.getElementById(containerId);
this.cy = null;
this.data = null;
this.options = Object.assign({
defaultLayout: 'cose-bilkent',
darkMode: document.documentElement.classList.contains('dark'),
apiEndpoint: '/api/mindmap',
onNodeClick: null,
enableEditing: true,
enableExport: true
}, options);
// Event-Listener für Dark Mode-Änderungen
document.addEventListener('darkModeToggled', (event) => {
this.options.darkMode = event.detail.isDark;
if (this.cy) {
this.updateStyles();
}
});
// Toolbar für Bearbeitungswerkzeuge
this.initToolbar();
}
/**
* Initialisiert die Toolbar mit Bearbeitungswerkzeugen
*/
initToolbar() {
if (!this.options.enableEditing) return;
const toolbar = document.createElement('div');
toolbar.className = 'mindmap-toolbar';
toolbar.innerHTML = `
${this.options.enableExport ? `
` : ''}
`;
this.container.parentNode.insertBefore(toolbar, this.container);
this.initToolbarEvents(toolbar);
}
/**
* Initialisiert Event-Listener für die Toolbar
*/
initToolbarEvents(toolbar) {
toolbar.querySelector('.add-node')?.addEventListener('click', () => this.addNode());
toolbar.querySelector('.add-child')?.addEventListener('click', () => this.addChildNode());
toolbar.querySelector('.connect-nodes')?.addEventListener('click', () => this.toggleConnectionMode());
toolbar.querySelector('.change-color')?.addEventListener('click', () => this.showColorPicker());
toolbar.querySelector('.delete-node')?.addEventListener('click', () => this.deleteSelectedNodes());
// Export-Funktionen
toolbar.querySelectorAll('.export-options button').forEach(btn => {
btn.addEventListener('click', () => this.exportMindmap(btn.dataset.format));
});
}
/**
* Initialisiert die Mindmap
*/
async initialize() {
console.log("Initialisiere Mindmap...");
try {
// Versuche, Daten vom Server zu laden
this.data = await this.loadDataFromServer();
console.log("Daten vom Server geladen:", this.data);
} catch (error) {
console.warn("Fehler beim Laden der Daten vom Server:", error);
console.log("Verwende Standarddaten als Fallback");
this.data = this.getDefaultData();
}
// Cytoscape initialisieren
this.initializeCytoscape();
// Drag-and-Drop Funktionalität aktivieren
this.enableDragAndDrop();
return this;
}
/**
* Initialisiert Cytoscape.js mit erweiterten Interaktionsmöglichkeiten
*/
initializeCytoscape() {
if (!this.container) {
console.error(`Container mit ID '${this.containerId}' nicht gefunden.`);
return;
}
// Konvertiert die API-Daten ins Cytoscape-Format
const elements = this.convertToCytoscapeFormat(this.data);
// Erstellt die Cytoscape-Instanz mit erweiterten Optionen
this.cy = cytoscape({
container: this.container,
elements: elements,
style: this.getStyles(),
layout: {
name: this.options.defaultLayout,
animate: true,
animationDuration: 800,
nodeDimensionsIncludeLabels: true,
padding: 30,
spacingFactor: 1.2,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 8000,
edgeElasticity: 100,
nestingFactor: 1.2,
gravity: 80
},
// Erweiterte Interaktionsoptionen
minZoom: 0.2,
maxZoom: 3,
wheelSensitivity: 0.3,
autounselectify: false,
selectionType: 'additive'
});
// Event-Listener für Interaktionen
this.initializeEventListeners();
}
/**
* Initialisiert Event-Listener für Interaktionen
*/
initializeEventListeners() {
// Knoten-Klick
this.cy.on('tap', 'node', (event) => {
const node = event.target;
this.handleNodeClick(node);
});
// Kontextmenü für Knoten
this.cy.on('cxttap', 'node', (event) => {
const node = event.target;
this.showContextMenu(node, event.renderedPosition);
});
// Drag-and-Drop Events
this.cy.on('dragfree', 'node', (event) => {
const node = event.target;
this.saveNodePosition(node);
});
// Zoom-Events für responsive Anpassungen
this.cy.on('zoom', () => {
this.adjustNodeStyling();
});
}
/**
* Fügt einen neuen Knoten hinzu
*/
addNode() {
const label = prompt('Name des neuen Knotens:');
if (!label) return;
const node = this.cy.add({
group: 'nodes',
data: {
id: 'n' + Date.now(),
name: label,
color: '#8B5CF6'
},
position: {
x: this.cy.width() / 2,
y: this.cy.height() / 2
}
});
this.saveToServer();
return node;
}
/**
* Fügt einen Unterknoten zum ausgewählten Knoten hinzu
*/
addChildNode() {
const selected = this.cy.$('node:selected');
if (selected.length !== 1) {
alert('Bitte wählen Sie genau einen Knoten aus.');
return;
}
const parent = selected[0];
const label = prompt('Name des Unterknotens:');
if (!label) return;
const child = this.cy.add({
group: 'nodes',
data: {
id: 'n' + Date.now(),
name: label,
color: parent.data('color')
},
position: {
x: parent.position('x') + 100,
y: parent.position('y') + 100
}
});
this.cy.add({
group: 'edges',
data: {
id: 'e' + Date.now(),
source: parent.id(),
target: child.id()
}
});
this.saveToServer();
}
/**
* Aktiviert/Deaktiviert den Verbindungsmodus
*/
toggleConnectionMode() {
this.connectionMode = !this.connectionMode;
this.cy.container().style.cursor = this.connectionMode ? 'crosshair' : 'default';
if (this.connectionMode) {
this.cy.once('tap', 'node', (e1) => {
const source = e1.target;
this.cy.once('tap', 'node', (e2) => {
const target = e2.target;
if (source.id() !== target.id()) {
this.cy.add({
group: 'edges',
data: {
id: 'e' + Date.now(),
source: source.id(),
target: target.id()
}
});
this.saveToServer();
}
this.connectionMode = false;
this.cy.container().style.cursor = 'default';
});
});
}
}
/**
* Zeigt den Farbwähler für ausgewählte Knoten
*/
showColorPicker() {
const selected = this.cy.$('node:selected');
if (selected.length === 0) {
alert('Bitte wählen Sie mindestens einen Knoten aus.');
return;
}
const colorPicker = document.createElement('input');
colorPicker.type = 'color';
colorPicker.value = selected[0].data('color') || '#8B5CF6';
colorPicker.style.position = 'absolute';
colorPicker.style.left = '-9999px';
document.body.appendChild(colorPicker);
colorPicker.addEventListener('change', (event) => {
const color = event.target.value;
selected.forEach(node => {
node.data('color', color);
});
this.saveToServer();
document.body.removeChild(colorPicker);
});
colorPicker.click();
}
/**
* Löscht ausgewählte Knoten
*/
deleteSelectedNodes() {
const selected = this.cy.$('node:selected');
if (selected.length === 0) {
alert('Bitte wählen Sie mindestens einen Knoten aus.');
return;
}
if (confirm(`${selected.length} Knoten löschen?`)) {
this.cy.remove(selected);
this.saveToServer();
}
}
/**
* Exportiert die Mindmap im gewählten Format
*/
exportMindmap(format) {
switch (format) {
case 'png':
const png = this.cy.png({
full: true,
scale: 2,
bg: this.options.darkMode ? '#1a1a1a' : '#ffffff'
});
this.downloadFile(png, 'mindmap.png', 'image/png');
break;
case 'json':
const json = JSON.stringify(this.cy.json(), null, 2);
this.downloadFile(json, 'mindmap.json', 'application/json');
break;
case 'pdf':
// Hier könnte eine PDF-Export-Implementierung folgen
alert('PDF-Export wird in Kürze verfügbar sein.');
break;
}
}
/**
* Hilfsfunktion zum Herunterladen von Dateien
*/
downloadFile(content, filename, type) {
const blob = type.startsWith('image') ? this.dataURLtoBlob(content) : new Blob([content], { type });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
}
/**
* Konvertiert Data URL zu Blob
*/
dataURLtoBlob(dataurl) {
const arr = dataurl.split(',');
const mime = arr[0].match(/:(.*?);/)[1];
const bstr = atob(arr[1]);
let n = bstr.length;
const u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
}
/**
* Speichert Änderungen auf dem Server
*/
async saveToServer() {
try {
const response = await fetch(this.options.apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(this.cy.json())
});
if (!response.ok) {
throw new Error(`HTTP Fehler: ${response.status}`);
}
console.log('Änderungen erfolgreich gespeichert');
} catch (error) {
console.error('Fehler beim Speichern:', error);
alert('Fehler beim Speichern der Änderungen');
}
}
}
// Globale Export
window.MindMap.Visualization = MindmapVisualization;
// Automatische Initialisierung, wenn das DOM geladen ist
document.addEventListener('DOMContentLoaded', function() {
const cyContainer = document.getElementById('cy');
if (cyContainer) {
console.log("Mindmap-Container gefunden, initialisiere...");
const mindmap = new MindmapVisualization('cy', {
onNodeClick: function(nodeData) {
console.log("Knoten ausgewählt:", nodeData);
}
});
mindmap.initialize().then(() => {
console.log("Mindmap erfolgreich initialisiert");
// Speichere die Instanz global für den Zugriff von außen
window.mindmap = mindmap;
}).catch(error => {
console.error("Fehler bei der Initialisierung der Mindmap:", error);
});
}
});