551 lines
16 KiB
JavaScript
551 lines
16 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...');
|
|
|
|
// Warte auf die Cytoscape-Instanz
|
|
document.addEventListener('mindmap-loaded', function() {
|
|
const cy = window.cy;
|
|
if (!cy) return;
|
|
|
|
// Event-Listener für Knoten-Klicks
|
|
cy.on('tap', 'node', function(evt) {
|
|
const node = evt.target;
|
|
|
|
// Alle vorherigen Hervorhebungen zurücksetzen
|
|
cy.nodes().forEach(n => {
|
|
n.removeStyle();
|
|
n.connectedEdges().removeStyle();
|
|
});
|
|
|
|
// Speichere ausgewählten Knoten
|
|
window.mindmapInstance.selectedNode = node;
|
|
|
|
// Aktiviere leuchtenden Effekt statt Umkreisung
|
|
node.style({
|
|
'background-opacity': 1,
|
|
'background-color': node.data('color'),
|
|
'shadow-color': node.data('color'),
|
|
'shadow-opacity': 1,
|
|
'shadow-blur': 15,
|
|
'shadow-offset-x': 0,
|
|
'shadow-offset-y': 0
|
|
});
|
|
|
|
// Verbundene Kanten und Knoten hervorheben
|
|
const connectedEdges = node.connectedEdges();
|
|
const connectedNodes = node.neighborhood('node');
|
|
|
|
connectedEdges.style({
|
|
'line-color': '#a78bfa',
|
|
'target-arrow-color': '#a78bfa',
|
|
'source-arrow-color': '#a78bfa',
|
|
'line-opacity': 0.8,
|
|
'width': 2
|
|
});
|
|
|
|
connectedNodes.style({
|
|
'shadow-opacity': 0.7,
|
|
'shadow-blur': 10,
|
|
'shadow-color': '#a78bfa'
|
|
});
|
|
|
|
// Info-Panel aktualisieren
|
|
updateInfoPanel(node);
|
|
|
|
// Seitenleiste aktualisieren
|
|
updateSidebar(node);
|
|
});
|
|
|
|
// Klick auf Hintergrund - Auswahl zurücksetzen
|
|
cy.on('tap', function(evt) {
|
|
if (evt.target === cy) {
|
|
resetSelection(cy);
|
|
}
|
|
});
|
|
|
|
// Zoom-Controls
|
|
document.getElementById('zoomIn')?.addEventListener('click', () => {
|
|
cy.zoom({
|
|
level: cy.zoom() * 1.2,
|
|
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
|
|
});
|
|
});
|
|
|
|
document.getElementById('zoomOut')?.addEventListener('click', () => {
|
|
cy.zoom({
|
|
level: cy.zoom() / 1.2,
|
|
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
|
|
});
|
|
});
|
|
|
|
document.getElementById('resetView')?.addEventListener('click', () => {
|
|
cy.fit();
|
|
resetSelection(cy);
|
|
});
|
|
|
|
// Legend-Toggle
|
|
document.getElementById('toggleLegend')?.addEventListener('click', () => {
|
|
const legend = document.getElementById('categoryLegend');
|
|
if (legend) {
|
|
isLegendVisible = !isLegendVisible;
|
|
legend.style.display = isLegendVisible ? 'block' : 'none';
|
|
}
|
|
});
|
|
|
|
// Keyboard-Controls
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === '+' || e.key === '=') {
|
|
cy.zoom({
|
|
level: cy.zoom() * 1.2,
|
|
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
|
|
});
|
|
} else if (e.key === '-' || e.key === '_') {
|
|
cy.zoom({
|
|
level: cy.zoom() / 1.2,
|
|
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
|
|
});
|
|
} else if (e.key === 'Escape') {
|
|
resetSelection(cy);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Aktualisiert das Info-Panel mit Knoteninformationen
|
|
* @param {Object} node - Der ausgewählte Knoten
|
|
*/
|
|
function updateInfoPanel(node) {
|
|
const infoPanel = document.getElementById('infoPanel');
|
|
if (!infoPanel) return;
|
|
|
|
const data = node.data();
|
|
const connectedNodes = node.neighborhood('node');
|
|
|
|
let html = `
|
|
<h3>${data.label || data.name}</h3>
|
|
<p class="category">${data.category || 'Keine Kategorie'}</p>
|
|
${data.description ? `<p class="description">${data.description}</p>` : ''}
|
|
<div class="connections">
|
|
<h4>Verbindungen (${connectedNodes.length})</h4>
|
|
<ul>
|
|
`;
|
|
|
|
connectedNodes.forEach(connectedNode => {
|
|
const connectedData = connectedNode.data();
|
|
html += `
|
|
<li style="color: ${connectedData.color || '#60a5fa'}">
|
|
${connectedData.label || connectedData.name}
|
|
</li>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</ul>
|
|
</div>
|
|
`;
|
|
|
|
infoPanel.innerHTML = html;
|
|
infoPanel.style.display = 'block';
|
|
}
|
|
|
|
/**
|
|
* Aktualisiert die Seitenleiste mit Knoteninformationen
|
|
* @param {Object} node - Der ausgewählte Knoten
|
|
*/
|
|
function updateSidebar(node) {
|
|
const sidebar = document.getElementById('sidebar');
|
|
if (!sidebar) return;
|
|
|
|
const data = node.data();
|
|
const connectedNodes = node.neighborhood('node');
|
|
|
|
let html = `
|
|
<div class="node-details">
|
|
<h3>${data.label || data.name}</h3>
|
|
<p class="category">${data.category || 'Keine Kategorie'}</p>
|
|
${data.description ? `<p class="description">${data.description}</p>` : ''}
|
|
<div class="connections">
|
|
<h4>Verbindungen (${connectedNodes.length})</h4>
|
|
<ul>
|
|
`;
|
|
|
|
connectedNodes.forEach(connectedNode => {
|
|
const connectedData = connectedNode.data();
|
|
html += `
|
|
<li style="color: ${connectedData.color || '#60a5fa'}">
|
|
${connectedData.label || connectedData.name}
|
|
</li>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
sidebar.innerHTML = html;
|
|
}
|
|
|
|
/**
|
|
* Setzt die Auswahl zurück
|
|
* @param {Object} cy - Cytoscape-Instanz
|
|
*/
|
|
function resetSelection(cy) {
|
|
window.mindmapInstance.selectedNode = null;
|
|
|
|
// Alle Hervorhebungen zurücksetzen
|
|
cy.nodes().forEach(node => {
|
|
node.removeStyle();
|
|
node.connectedEdges().removeStyle();
|
|
});
|
|
|
|
// Info-Panel ausblenden
|
|
const infoPanel = document.getElementById('infoPanel');
|
|
if (infoPanel) {
|
|
infoPanel.style.display = 'none';
|
|
}
|
|
|
|
// Seitenleiste leeren
|
|
const sidebar = document.getElementById('sidebar');
|
|
if (sidebar) {
|
|
sidebar.innerHTML = '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)];
|
|
}
|
|
|
|
// Initialisiere die Mindmap-Seite
|
|
initMindmapPage();
|