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

438 lines
13 KiB
JavaScript

/**
* Mindmap-Seite JavaScript
* Spezifische Funktionen für die Mindmap-Seite
*/
// Füge das Modul zum globalen MindMap-Objekt hinzu
if (!window.MindMap) {
window.MindMap = {};
}
// Registriere den Initialisierer im MindMap-Objekt
window.MindMap.pageInitializers = window.MindMap.pageInitializers || {};
window.MindMap.pageInitializers.mindmap = initMindmapPage;
// Event-Listener für DOMContentLoaded
document.addEventListener('DOMContentLoaded', function() {
// Prüfe, ob wir auf der Mindmap-Seite sind
if (document.body && document.body.dataset && document.body.dataset.page === 'mindmap') {
initMindmapPage();
}
});
/**
* Initialisiert die Mindmap-Seite
*/
function initMindmapPage() {
console.log('Mindmap-Seite wird initialisiert...');
// Hauptcontainer für die Mindmap
const cyContainer = document.getElementById('cy');
if (!cyContainer) {
console.error('Mindmap-Container #cy nicht gefunden!');
return;
}
// Info-Panel für Knotendetails
const nodeInfoPanel = document.getElementById('node-info-panel');
const nodeDescription = document.getElementById('node-description');
const connectedNodes = document.getElementById('connected-nodes');
// Toolbar-Buttons
const fitButton = document.getElementById('fit-btn');
const resetButton = document.getElementById('reset-btn');
const toggleLabelsButton = document.getElementById('toggle-labels-btn');
// Mindmap-Instanz
let mindmap = null;
// Cytoscape.js für die Visualisierung initialisieren
try {
// Cytoscape.js-Bibliothek überprüfen
if (typeof cytoscape === 'undefined') {
loadExternalScript('https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js')
.then(() => {
initCytoscape();
})
.catch(error => {
console.error('Fehler beim Laden von Cytoscape.js:', error);
showErrorMessage(cyContainer, 'Cytoscape.js konnte nicht geladen werden.');
});
} else {
initCytoscape();
}
} catch (error) {
console.error('Fehler bei der Initialisierung der Mindmap:', error);
showErrorMessage(cyContainer, 'Die Mindmap konnte nicht initialisiert werden: ' + error.message);
}
/**
* Lädt ein externes Script asynchron
*/
function loadExternalScript(url) {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
}
/**
* Zeigt eine Fehlermeldung im Container an
*/
function showErrorMessage(container, message) {
container.innerHTML = `
<div class="p-8 text-center bg-red-50 dark:bg-red-900/20 rounded-lg">
<i class="fa-solid fa-triangle-exclamation text-4xl text-red-500 mb-4"></i>
<p class="text-lg text-red-600 dark:text-red-400">${message}</p>
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Bitte laden Sie die Seite neu oder kontaktieren Sie den Support.</p>
</div>
`;
}
/**
* Initialisiert Cytoscape mit den Mindmap-Daten
*/
function initCytoscape() {
console.log('Cytoscape.js wird initialisiert...');
// Zeige Ladeanimation
cyContainer.innerHTML = `
<div class="flex justify-center items-center h-full">
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-purple-500"></div>
</div>
`;
// Lade Daten vom Backend
fetch('/api/mindmap')
.then(response => {
if (!response.ok) {
throw new Error('Netzwerkfehler beim Laden der Mindmap-Daten');
}
return response.json();
})
.then(data => {
console.log('Mindmap-Daten erfolgreich geladen');
renderMindmap(data);
})
.catch(error => {
console.error('Fehler beim Laden der Mindmap-Daten:', error);
// Verwende Standarddaten als Fallback
console.log('Verwende Standarddaten als Fallback...');
const defaultData = generateDefaultData();
renderMindmap(defaultData);
});
}
/**
* Generiert Standarddaten für die Mindmap als Fallback
*/
function generateDefaultData() {
return {
nodes: [
{ id: 'root', name: 'Wissen', description: 'Zentrale Wissensbasis', category: 'Zentral', color_code: '#4299E1' },
{ id: 'philosophy', name: 'Philosophie', description: 'Philosophisches Denken', category: 'Philosophie', color_code: '#9F7AEA', parent_id: 'root' },
{ id: 'science', name: 'Wissenschaft', description: 'Wissenschaftliche Erkenntnisse', category: 'Wissenschaft', color_code: '#48BB78', parent_id: 'root' },
{ id: 'technology', name: 'Technologie', description: 'Technologische Entwicklungen', category: 'Technologie', color_code: '#ED8936', parent_id: 'root' },
{ id: 'arts', name: 'Künste', description: 'Künstlerische Ausdrucksformen', category: 'Künste', color_code: '#ED64A6', parent_id: 'root' },
{ id: 'psychology', name: 'Psychologie', description: 'Menschliches Verhalten und Geist', category: 'Psychologie', color_code: '#4299E1', parent_id: 'root' }
]
};
}
/**
* Rendert die Mindmap mit Cytoscape.js
*/
function renderMindmap(data) {
console.log('Rendere Mindmap mit Daten:', data);
// Konvertiere Backend-Daten in Cytoscape-Format
const elements = convertToCytoscapeFormat(data);
// Leere den Container
cyContainer.innerHTML = '';
// Erstelle Cytoscape-Instanz
mindmap = cytoscape({
container: cyContainer,
elements: elements,
style: [
{
selector: 'node',
style: {
'background-color': 'data(color)',
'label': 'data(name)',
'width': 30,
'height': 30,
'font-size': 12,
'text-valign': 'bottom',
'text-halign': 'center',
'text-margin-y': 8,
'color': document.documentElement.classList.contains('dark') ? '#f1f5f9' : '#334155',
'text-background-color': document.documentElement.classList.contains('dark') ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)',
'text-background-opacity': 0.8,
'text-background-padding': '2px',
'text-background-shape': 'roundrectangle',
'text-wrap': 'ellipsis',
'text-max-width': '100px'
}
},
{
selector: 'edge',
style: {
'width': 2,
'line-color': document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'target-arrow-color': document.documentElement.classList.contains('dark') ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
'curve-style': 'bezier'
}
},
{
selector: 'node:selected',
style: {
'background-color': 'data(color)',
'border-width': 3,
'border-color': '#8b5cf6',
'width': 40,
'height': 40,
'font-size': 14,
'font-weight': 'bold',
'text-background-color': '#8b5cf6',
'text-background-opacity': 0.9
}
}
],
layout: {
name: 'cose',
animate: true,
animationDuration: 800,
nodeDimensionsIncludeLabels: true,
refresh: 30,
randomize: true,
componentSpacing: 100,
nodeRepulsion: 8000,
nodeOverlap: 20,
idealEdgeLength: 200,
edgeElasticity: 100,
nestingFactor: 1.2,
gravity: 80,
fit: true,
padding: 30
}
});
// Event-Listener für Knoteninteraktionen
mindmap.on('tap', 'node', function(evt) {
const node = evt.target;
const nodeData = node.data();
// Update Info-Panel
updateNodeInfoPanel(nodeData);
// Lade verbundene Knoten
updateConnectedNodes(node);
});
// Toolbar-Buttons aktivieren
if (fitButton) {
fitButton.addEventListener('click', () => {
mindmap.fit();
mindmap.center();
});
}
if (resetButton) {
resetButton.addEventListener('click', () => {
mindmap.layout({
name: 'cose',
animate: true,
randomize: true,
fit: true
}).run();
});
}
if (toggleLabelsButton) {
let labelsVisible = true;
toggleLabelsButton.addEventListener('click', () => {
labelsVisible = !labelsVisible;
if (labelsVisible) {
mindmap.style()
.selector('node')
.style('label', 'data(name)')
.update();
} else {
mindmap.style()
.selector('node')
.style('label', '')
.update();
}
});
}
// Dark Mode-Änderungen überwachen
document.addEventListener('darkModeToggled', function(event) {
updateDarkModeStyles(event.detail.isDark);
});
// Initial fit und center
setTimeout(() => {
mindmap.fit();
mindmap.center();
}, 100);
}
/**
* Konvertiert die Backend-Daten ins Cytoscape-Format
*/
function convertToCytoscapeFormat(data) {
const elements = {
nodes: [],
edges: []
};
// Nodes hinzufügen
if (data.nodes && data.nodes.length > 0) {
data.nodes.forEach(node => {
elements.nodes.push({
data: {
id: String(node.id),
name: node.name,
description: node.description || 'Keine Beschreibung verfügbar',
category: node.category || 'Allgemein',
color: node.color_code || getRandomColor()
}
});
// Kante zum Elternknoten hinzufügen (falls vorhanden)
if (node.parent_id) {
elements.edges.push({
data: {
id: `edge-${node.parent_id}-${node.id}`,
source: String(node.parent_id),
target: String(node.id)
}
});
}
});
// Zusätzliche Kanten zwischen Knoten hinzufügen (falls in den Daten vorhanden)
if (data.edges && data.edges.length > 0) {
data.edges.forEach(edge => {
elements.edges.push({
data: {
id: `edge-${edge.source}-${edge.target}`,
source: String(edge.source),
target: String(edge.target)
}
});
});
}
}
return elements;
}
/**
* Aktualisiert das Informations-Panel mit den Knotendaten
*/
function updateNodeInfoPanel(nodeData) {
if (nodeInfoPanel && nodeDescription) {
// Panel anzeigen
nodeInfoPanel.style.display = 'block';
// Titel und Beschreibung aktualisieren
const titleElement = nodeInfoPanel.querySelector('.info-panel-title');
if (titleElement) {
titleElement.textContent = nodeData.name;
}
if (nodeDescription) {
nodeDescription.textContent = nodeData.description || 'Keine Beschreibung verfügbar';
}
}
}
/**
* Aktualisiert die Liste der verbundenen Knoten
*/
function updateConnectedNodes(node) {
if (connectedNodes) {
// Leere den Container
connectedNodes.innerHTML = '';
// Hole verbundene Knoten
const connectedEdges = node.connectedEdges();
if (connectedEdges.length === 0) {
connectedNodes.innerHTML = '<div class="text-sm italic">Keine verbundenen Knoten</div>';
return;
}
// Füge alle verbundenen Knoten hinzu
connectedEdges.forEach(edge => {
const targetNode = edge.target().id() === node.id() ? edge.source() : edge.target();
const targetData = targetNode.data();
const nodeLink = document.createElement('div');
nodeLink.className = 'node-link';
nodeLink.innerHTML = `
<span class="w-2 h-2 rounded-full" style="background-color: ${targetData.color}"></span>
${targetData.name}
`;
// Klick-Event zum Fokussieren des Knotens
nodeLink.addEventListener('click', () => {
mindmap.center(targetNode);
targetNode.select();
updateNodeInfoPanel(targetData);
updateConnectedNodes(targetNode);
});
connectedNodes.appendChild(nodeLink);
});
}
}
/**
* Aktualisiert die Styles bei Dark Mode-Änderungen
*/
function updateDarkModeStyles(isDark) {
if (!mindmap) return;
const textColor = isDark ? '#f1f5f9' : '#334155';
const textBgColor = isDark ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)';
const edgeColor = isDark ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)';
mindmap.style()
.selector('node')
.style({
'color': textColor,
'text-background-color': textBgColor
})
.selector('edge')
.style({
'line-color': edgeColor,
'target-arrow-color': edgeColor
})
.update();
}
/**
* Generiert eine zufällige Farbe
*/
function getRandomColor() {
const colors = [
'#4299E1', // Blau
'#9F7AEA', // Lila
'#48BB78', // Grün
'#ED8936', // Orange
'#ED64A6', // Pink
'#F56565' // Rot
];
return colors[Math.floor(Math.random() * colors.length)];
}
}