Files
website/static/js/modules/mindmap.js

440 lines
28 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 = `
<button class="add-node" title="Neuen Knoten hinzufügen">
<i class="fas fa-plus"></i>
</button>
<button class="add-child" title="Unterknoten hinzufügen">
<i class="fas fa-sitemap"></i>
</button>
<button class="connect-nodes" title="Knoten verbinden">
<i class="fas fa-link"></i>
</button>
<button class="change-color" title="Farbe ändern">
<i class="fas fa-palette"></i>
</button>
<button class="delete-node" title="Knoten löschen">
<i class="fas fa-trash"></i>
</button>
${this.options.enableExport ? `
<div class="export-group">
<button class="export-mindmap" title="Exportieren">
<i class="fas fa-download"></i>
</button>
<div class="export-options">
<button data-format="png">Als Bild (.png)</button>
<button data-format="json">Als JSON</button>
<button data-format="pdf">Als PDF</button>
</div>
</div>
` : ''}
`;
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);
});
}
});