/** * 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); }); } });