/** * Update Mindmap * Dieses Skript fügt Knoten zur Mindmap hinzu und stellt sicher, * dass sie im neuronalen Netzwerk-Design angezeigt werden. * Implementiert Lazy Loading & Progressive Disclosure */ // Neue zentrale Konfiguration const mindmapConfig = { categories: { 'Philosophie': { icon: 'fa-solid fa-lightbulb', color: '#b71c1c', description: 'Die Lehre vom Denken und der Erkenntnis' }, 'Wissenschaft': { icon: 'fa-solid fa-atom', color: '#f4b400', description: 'Systematische Erforschung der Natur und Gesellschaft' }, 'Technologie': { icon: 'fa-solid fa-microchip', color: '#0d47a1', description: 'Anwendung wissenschaftlicher Erkenntnisse' }, 'Künste': { icon: 'fa-solid fa-palette', color: '#c2185b', description: 'Kreativer Ausdruck und künstlerische Gestaltung' } }, defaultNodeStyle: { fontSize: 18, fontColor: '#fff', neuronSize: 8, neuronActivity: 0.8 }, centerNodeStyle: { fontSize: 22, fontColor: '#222', neuronSize: 12, neuronActivity: 1.0, color: '#f5f5f5', icon: 'fa-solid fa-circle' } }; // Zentrale Styling-Konfiguration const mindmapStyles = { node: { base: { 'background-color': 'data(color)', 'label': 'data(label)', 'color': '#ffffff', 'text-background-color': 'rgba(0, 0, 0, 0.7)', 'text-background-opacity': 0.8, 'text-background-padding': '4px', 'text-valign': 'center', 'text-halign': 'center', 'font-size': 16, 'width': 40, 'height': 40, 'border-width': 2, 'border-color': '#ffffff', 'border-opacity': 0.8, 'shape': 'ellipse', 'background-opacity': 0.85 }, center: { 'background-color': '#f5f5f5', 'color': '#222', 'font-size': 20, 'border-width': 3, 'width': 100, 'height': 100 }, selected: { 'border-color': '#f59e42', 'border-width': 3, 'background-opacity': 1 } }, edge: { base: { 'width': function(ele) { return ele.data('strength') ? ele.data('strength') * 2 : 1; }, 'line-color': function(ele) { const sourceColor = ele.source().data('color'); return sourceColor || '#8a8aaa'; }, 'line-opacity': function(ele) { return ele.data('strength') ? ele.data('strength') * 0.6 : 0.4; }, 'curve-style': 'bezier', 'target-arrow-shape': 'none', 'control-point-distances': [30, -30], 'control-point-weights': [0.5, 0.5] } }, layout: { base: { name: 'cose', animate: true, animationDuration: 500, refresh: 20, fit: true, padding: 30, nodeRepulsion: 4500, idealEdgeLength: 50, edgeElasticity: 0.45, randomize: true, componentSpacing: 100, nodeOverlap: 20, gravity: 0.25, initialTemp: 1000, coolingFactor: 0.95, minTemp: 1 } } }; // Globale Variable für die Mindmap-Daten let mindmapData = null; // Funktion zum Laden der Mindmap-Daten aus der Datenbank async function loadMindmapData(nodeId = null) { try { const apiUrl = nodeId ? `/api/mindmap/${nodeId}` : '/api/mindmap/root'; console.log('Lade Mindmap-Daten von:', apiUrl); const response = await fetch(apiUrl); console.log('API-Antwort Status:', response.status); if (!response.ok) { let errorData; try { errorData = await response.json(); console.log('API-Fehler Details:', errorData); } catch (e) { console.error('Fehler beim Parsen der Fehlerantwort:', e); errorData = { error: `HTTP-Fehler ${response.status}: ${response.statusText}` }; } // Fehlerobjekt für die Benachrichtigung erstellen const errorMessage = errorData.error || 'Unbekannter Fehler'; showUINotification(errorMessage, 'error'); throw new Error(errorMessage); } const data = await response.json(); console.log('Geladene Mindmap-Daten:', data); if (!data.success) { const errorMessage = data.error || 'Mindmap-Daten konnten nicht geladen werden'; showUINotification(errorMessage, 'error'); throw new Error(errorMessage); } // Überprüfen, ob Nodes und Edges existieren if (!data.nodes || !data.edges) { const errorMessage = 'Ungültiges Datenformat: Nodes oder Edges fehlen'; showUINotification(errorMessage, 'error'); throw new Error(errorMessage); } // Erfolgreiche Antwort mindmapData = data; // Speichere die Daten in der globalen Variable showUINotification('Mindmap-Daten erfolgreich geladen', 'success'); return data; } catch (error) { console.error('Fehler beim Laden der Mindmap-Daten:', error); // Stelle sicher, dass wir eine aussagekräftige Fehlermeldung haben const errorMessage = error.message || 'Unbekannter Fehler beim Laden der Mindmap-Daten'; showUINotification(errorMessage, 'error'); throw error; } } /** * Zerstört eine Cytoscape-Instanz sicher * @param {Object} instance - Die zu zerstörende Cytoscape-Instanz */ function destroyCytoscapeInstance(instance) { if (instance && typeof instance.destroy === 'function') { try { // Event-Listener entfernen instance.removeAllListeners(); // Instanz zerstören instance.destroy(); } catch (e) { console.error('Fehler beim Zerstören der Cytoscape-Instanz:', e); } } } // Funktion zum Initialisieren der Mindmap async function initializeMindmap() { try { // Zeige Ladeanimation const loader = document.getElementById('loader'); const statusMessage = document.getElementById('statusMessage'); if (loader) loader.style.display = 'block'; if (statusMessage) { statusMessage.textContent = 'Lade Mindmap...'; statusMessage.style.display = 'block'; } // Prüfe auf bestehende Cytoscape-Instanz und zerstöre sie if (window.cy && typeof window.cy.destroy === 'function') { try { window.cy.destroy(); } catch (e) { console.error('Fehler beim Zerstören der bestehenden Cytoscape-Instanz:', e); } window.cy = null; } // Bereinige auch alle Subthemen-Instanzen if (window.subthemeCyInstances) { Object.keys(window.subthemeCyInstances).forEach(key => { try { if (window.subthemeCyInstances[key] && typeof window.subthemeCyInstances[key].destroy === 'function') { window.subthemeCyInstances[key].destroy(); } } catch (e) { console.error(`Fehler beim Zerstören der Subthemen-Instanz ${key}:`, e); } }); window.subthemeCyInstances = {}; } // Entferne bestehende Subpages aus dem DOM const existingSubpages = document.querySelectorAll('.mindmap-subpage'); existingSubpages.forEach(subpage => { if (subpage.parentNode) { subpage.parentNode.removeChild(subpage); } }); // Lade Mindmap-Daten const data = await loadMindmapData(); if (!data || !data.nodes || !data.edges) { throw new Error('Ungültiges Datenformat: Mindmap-Daten fehlen oder sind unvollständig'); } // Prüfe, ob der Container existiert const cyContainer = document.getElementById('cy'); if (!cyContainer) { throw new Error('Mindmap-Container #cy nicht gefunden!'); } // Stelle sicher, dass der Container sichtbar ist cyContainer.style.display = 'block'; // Elemente für Cytoscape erstellen const elements = [ // Knoten ...data.nodes.map(node => ({ data: { id: node.id, label: node.name, category: node.category, description: node.description, hasChildren: node.has_children, expanded: false, color: node.color_code, fontColor: '#ffffff', fontSize: node.is_center ? 20 : 16 } })), // Kanten ...data.edges.map(edge => ({ data: { source: edge.source, target: edge.target, strength: edge.strength || 0.5 } })) ]; // Cytoscape-Instanz erstellen window.cy = cytoscape({ container: cyContainer, elements: elements, style: [ { selector: 'node', style: mindmapStyles.node.base }, { selector: 'node[isCenter]', style: mindmapStyles.node.center }, { selector: 'node:selected', style: mindmapStyles.node.selected }, { selector: 'edge', style: mindmapStyles.edge.base } ], layout: mindmapStyles.layout.base, // Mausrad-Zooming deaktivieren wheelSensitivity: 0, minZoom: 0.2, maxZoom: 2.5 }); // Prüfe, ob die Cytoscape-Instanz erfolgreich erstellt wurde if (!window.cy || !window.cy.container()) { throw new Error('Cytoscape-Instanz konnte nicht korrekt initialisiert werden'); } // Füge neuronale Eigenschaften zu allen Knoten hinzu window.cy.nodes().forEach(node => { const data = node.data(); // Verwende mindmapConfig für Kategorie-Farben oder einen Standardwert const categoryColor = data.category && mindmapConfig.categories[data.category] ? mindmapConfig.categories[data.category].color : '#60a5fa'; node.data({ ...data, neuronSize: data.neuronSize || 8, neuronActivity: data.neuronActivity || 0.8, refractionPeriod: Math.random() * 300 + 700, threshold: Math.random() * 0.3 + 0.6, lastFired: 0, color: data.color || categoryColor }); }); // Füge synaptische Eigenschaften zu allen Kanten hinzu window.cy.edges().forEach(edge => { const data = edge.data(); edge.data({ ...data, strength: data.strength || 0.5, conductionVelocity: Math.random() * 0.5 + 0.3, latency: Math.random() * 100 + 50 }); }); // Event-Listener für Knoten-Klicks mit Fehlerbehandlung window.cy.on('tap', 'node', async function(evt) { try { const node = evt.target; console.log('Node clicked:', node.id(), 'hasChildren:', node.data('hasChildren') || node.data('has_children'), 'expanded:', node.data('expanded')); if ((node.data('hasChildren') || node.data('has_children')) && !node.data('expanded')) { await loadSubthemes(node); } } catch (e) { console.error('Fehler beim Node-Tap-Event:', e); showUINotification('Fehler beim Verarbeiten des Klicks: ' + e.message, 'error'); } }); // Event-Listener für Hover über Knoten (für Info-Panel) window.cy.on('mouseover', 'node', function(evt) { try { const node = evt.target; showNodeInfo(node); } catch (e) { console.error('Fehler beim Mouseover-Event:', e); } }); window.cy.on('mouseout', 'node', function() { try { hideNodeInfo(); } catch (e) { console.error('Fehler beim Mouseout-Event:', e); } }); // Layout ausführen window.cy.layout(mindmapStyles.layout.base).run(); // Starte neuronale Aktivitätssimulation try { startNeuralActivitySimulation(window.cy); } catch (e) { console.error('Fehler beim Starten der neuronalen Simulation:', e); } // Mindmap mit echten Daten befüllen (Styles, Farben etc.) try { updateMindmap(); } catch (e) { console.error('Fehler beim Aktualisieren der Mindmap:', e); } // Setze anfänglichen Zoom auf eine kleinere Stufe, damit mehr sichtbar ist setTimeout(() => { try { window.cy.zoom({ level: 0.7, renderedPosition: { x: window.cy.width() / 2, y: window.cy.height() / 2 } }); window.cy.center(); } catch (e) { console.error('Fehler beim Setzen des anfänglichen Zooms:', e); } }, 300); // Verstecke Ladeanimation if (loader) loader.style.display = 'none'; if (statusMessage) statusMessage.style.display = 'none'; // Event auslösen, damit andere Scripte reagieren können document.dispatchEvent(new Event('mindmap-loaded')); console.log('mindmap-loaded Event ausgelöst'); return true; } catch (error) { console.error('Fehler bei der Mindmap-Initialisierung:', error); // Verstecke Ladeanimation const loader = document.getElementById('loader'); const statusMessage = document.getElementById('statusMessage'); if (loader) loader.style.display = 'none'; // Zeige Fehlermeldung if (statusMessage) { statusMessage.textContent = 'Fehler beim Laden der Mindmap: ' + error.message; statusMessage.style.backgroundColor = 'rgba(220, 38, 38, 0.9)'; statusMessage.style.display = 'block'; } showUINotification({ error: 'Mindmap konnte nicht initialisiert werden', details: error.message }, 'error'); return false; } } // Warte bis DOM geladen ist document.addEventListener('DOMContentLoaded', function() { console.log('DOMContentLoaded Event ausgelöst'); // Prüfe, ob der Container existiert const cyContainer = document.getElementById('cy'); console.log('Container gefunden:', cyContainer); if (!cyContainer) { console.error('Mindmap-Container #cy nicht gefunden!'); return; } // Prüfe, ob Cytoscape verfügbar ist if (typeof cytoscape === 'undefined') { console.error('Cytoscape ist nicht definiert!'); // Versuche, Cytoscape nachzuladen const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js'; script.onload = function() { console.log('Cytoscape wurde nachgeladen'); initializeMindmap().catch(console.error); }; script.onerror = function() { console.error('Cytoscape konnte nicht nachgeladen werden'); showUINotification('Cytoscape-Bibliothek konnte nicht geladen werden', 'error'); }; document.head.appendChild(script); return; } console.log('Cytoscape ist verfügbar'); // Initialisiere die Mindmap mit Fehlerbehandlung initializeMindmap() .then(success => { if (success) { console.log('Mindmap wurde erfolgreich initialisiert'); // Event auslösen, damit andere Scripte reagieren können document.dispatchEvent(new Event('mindmap-loaded')); console.log('mindmap-loaded Event ausgelöst'); } else { console.error('Mindmap-Initialisierung fehlgeschlagen'); } }) .catch(error => { console.error('Fehler bei der Mindmap-Initialisierung:', error); showUINotification({ error: 'Mindmap konnte nicht initialisiert werden', details: error.message }, 'error'); }); }); // Funktion zum Initialisieren des neuronalen Designs function initializeNeuralDesign(cy) { // Füge neuronale Eigenschaften zu allen Knoten hinzu cy.nodes().forEach(node => { const data = node.data(); // Verwende mindmapConfig für Kategorie-Farben oder einen Standardwert const categoryColor = data.category && mindmapConfig.categories[data.category] ? mindmapConfig.categories[data.category].color : '#60a5fa'; node.data({ ...data, neuronSize: data.neuronSize || 8, neuronActivity: data.neuronActivity || 0.8, refractionPeriod: Math.random() * 300 + 700, threshold: Math.random() * 0.3 + 0.6, lastFired: 0, color: data.color || categoryColor }); }); // Füge synaptische Eigenschaften zu allen Kanten hinzu cy.edges().forEach(edge => { const data = edge.data(); edge.data({ ...data, strength: data.strength || 0.5, conductionVelocity: Math.random() * 0.5 + 0.3, latency: Math.random() * 100 + 50 }); }); // Wende neuronales Styling an cy.style() .selector('node') .style({ 'background-color': 'data(color)', 'label': 'data(label)', 'color': '#fff', 'text-background-color': 'rgba(0, 0, 0, 0.7)', 'text-background-opacity': 0.8, 'text-background-padding': '4px', 'text-valign': 'center', 'text-halign': 'center', 'font-size': 16, 'width': 40, 'height': 40, 'border-width': 2, 'border-color': '#fff', 'border-opacity': 0.8, 'overlay-padding': 4, 'z-index': 10, 'shape': 'ellipse', 'background-opacity': 0.85, 'shadow-blur': 15, 'shadow-color': 'data(color)', 'shadow-opacity': 0.6, 'shadow-offset-x': 0, 'shadow-offset-y': 0 }) .selector('edge') .style({ 'width': function(ele) { return ele.data('strength') ? ele.data('strength') * 3 : 1; }, 'curve-style': 'bezier', 'line-color': function(ele) { const sourceColor = ele.source().data('color'); return sourceColor || '#8a8aaa'; }, 'line-opacity': function(ele) { return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; }, 'line-style': function(ele) { const strength = ele.data('strength'); if (!strength) return 'solid'; if (strength <= 0.4) return 'dotted'; if (strength <= 0.6) return 'dashed'; return 'solid'; }, 'target-arrow-shape': 'none', 'source-endpoint': '0% 50%', 'target-endpoint': '100% 50%', 'transition-property': 'line-opacity, width', 'transition-duration': '0.3s', 'transition-timing-function': 'ease-in-out' }) .update(); // Starte neuronale Aktivitätssimulation startNeuralActivitySimulation(cy); } // Modifiziere die updateMindmap Funktion function updateMindmap() { if (!cy) return; // Bestehende Elemente entfernen cy.elements().remove(); // Neue Knoten hinzufügen mindmapData.nodes.forEach(node => { cy.add({ group: 'nodes', data: { id: node.id, label: node.name, category: node.category, description: node.description, has_children: node.has_children, hasChildren: node.has_children, // Doppelte Eigenschaft für Kompatibilität expanded: false, color: node.color_code || mindmapConfig.categories[node.category]?.color || '#60a5fa', icon: node.icon || mindmapConfig.categories[node.category]?.icon || 'fa-solid fa-circle' } }); }); // Neue Kanten hinzufügen mindmapData.edges.forEach(edge => { cy.add({ group: 'edges', data: { source: edge.source, target: edge.target, strength: edge.strength || 0.5 } }); }); // Layout aktualisieren cy.layout(mindmapStyles.layout.base).run(); } /** * Erweitert die Mindmap mit dem neuronalen Netzwerk-Design */ function enhanceMindmap() { // Auf die bestehende Cytoscape-Instanz zugreifen const cy = window.cy; if (!cy) { console.error('Keine Cytoscape-Instanz gefunden.'); return; } // Aktualisiere das Layout für eine bessere Verteilung cy.layout({ name: 'cose', animate: true, animationDuration: 2000, nodeDimensionsIncludeLabels: true, padding: 100, spacingFactor: 2, randomize: true, fit: true, componentSpacing: 150, nodeRepulsion: 10000, edgeElasticity: 150, nestingFactor: 1.5, gravity: 100, initialTemp: 1000, coolingFactor: 0.95, minTemp: 1 }).run(); // Neuronen-Namen mit besserer Lesbarkeit umgestalten cy.style() .selector('node') .style({ 'text-background-color': 'rgba(10, 14, 25, 0.7)', 'text-background-opacity': 0.7, 'text-background-padding': '2px', 'text-border-opacity': 0.2, 'text-border-width': 1, 'text-border-color': '#8b5cf6' }) .update(); // Sicherstellen, dass alle Knoten Neuronen-Eigenschaften haben cy.nodes().forEach(node => { if (!node.data('neuronSize')) { const neuronSize = Math.floor(Math.random() * 8) + 3; node.data('neuronSize', neuronSize); } if (!node.data('neuronActivity')) { const neuronActivity = Math.random() * 0.7 + 0.3; node.data('neuronActivity', neuronActivity); } // Zusätzliche Neuronale Eigenschaften node.data('pulseFrequency', Math.random() * 4 + 2); // Pulsfrequenz (2-6 Hz) node.data('refractionPeriod', Math.random() * 300 + 700); // Refraktionszeit (700-1000ms) node.data('threshold', Math.random() * 0.3 + 0.6); // Aktivierungsschwelle (0.6-0.9) }); // Sicherstellen, dass alle Kanten Synapse-Eigenschaften haben cy.edges().forEach(edge => { if (!edge.data('strength')) { const strength = Math.random() * 0.6 + 0.2; edge.data('strength', strength); } // Zusätzliche synaptische Eigenschaften edge.data('conductionVelocity', Math.random() * 0.5 + 0.3); // Leitungsgeschwindigkeit (0.3-0.8) edge.data('latency', Math.random() * 100 + 50); // Signalverzögerung (50-150ms) }); // Neuronales Netzwerk-Stil anwenden applyNeuralNetworkStyle(cy); console.log('Mindmap wurde erfolgreich im neuronalen Netzwerk-Stil aktualisiert'); // Spezielle Effekte für das neuronale Netzwerk hinzufügen startNeuralActivitySimulation(cy); } /** * Wendet detaillierte neuronale Netzwerkstile auf die Mindmap an * @param {Object} cy - Cytoscape-Instanz */ function applyNeuralNetworkStyle(cy) { cy.style() .selector('node') .style({ 'label': 'data(label)', 'text-valign': 'center', 'text-halign': 'center', 'color': 'data(fontColor)', 'text-outline-width': 2, 'text-outline-color': 'rgba(0,0,0,0.8)', 'text-outline-opacity': 0.9, 'font-size': 'data(fontSize)', 'font-weight': '500', 'text-margin-y': 8, 'width': function(ele) { if (ele.data('isCenter')) return 120; return 80; }, 'height': function(ele) { if (ele.data('isCenter')) return 120; return 80; }, 'background-color': 'data(color)', 'background-opacity': 0.9, 'border-width': 2, 'border-color': '#ffffff', 'border-opacity': 0.8, 'shape': 'ellipse', 'transition-property': 'background-color, background-opacity, border-width', 'transition-duration': '0.3s', 'transition-timing-function': 'ease-in-out' }) .selector('edge') .style({ 'width': function(ele) { return ele.data('strength') ? ele.data('strength') * 3 : 1; }, 'curve-style': 'bezier', 'line-color': function(ele) { const sourceColor = ele.source().data('color'); return sourceColor || '#8a8aaa'; }, 'line-opacity': function(ele) { return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; }, 'line-style': function(ele) { const strength = ele.data('strength'); if (!strength) return 'solid'; if (strength <= 0.4) return 'dotted'; if (strength <= 0.6) return 'dashed'; return 'solid'; }, 'target-arrow-shape': 'none', 'source-endpoint': '0% 50%', 'target-endpoint': '100% 50%', 'transition-property': 'line-opacity, width', 'transition-duration': '0.3s', 'transition-timing-function': 'ease-in-out' }) .update(); } // Vereinfachte neuronale Aktivitätssimulation function startNeuralActivitySimulation(cy) { if (!cy || !cy.nodes) { console.error('Keine gültige Cytoscape-Instanz für neuronale Simulation'); return; } // Bestehende Intervalle stoppen, um Speicherlecks zu vermeiden if (window.neuralIntervals) { window.neuralIntervals.forEach(clearInterval); } // Erstelle ein Array für die Intervalle dieser Instanz window.neuralIntervals = window.neuralIntervals || []; const nodes = cy.nodes(); let currentTime = Date.now(); // Sicherer Zugriff auf Nodes mit Fehlerbehandlung function simulateNeuralActivity() { try { if (!cy || !cy.nodes) { console.warn('Cytoscape-Instanz nicht mehr verfügbar, stoppe Simulation'); clearInterval(intervalId); const index = window.neuralIntervals.indexOf(intervalId); if (index > -1) { window.neuralIntervals.splice(index, 1); } return; } currentTime = Date.now(); cy.nodes().forEach(node => { try { const data = node.data(); if (!data) return; const lastFired = data.lastFired || 0; const timeSinceLastFire = currentTime - lastFired; if (timeSinceLastFire > data.refractionPeriod) { if (Math.random() < data.neuronActivity * 0.1) { fireNeuron(node, true, currentTime); } } } catch (e) { // Ignoriere Fehler für einzelne Nodes console.debug('Fehler bei Node in Simulation:', e); } }); } catch (e) { console.error('Fehler in neuronaler Simulation:', e); } } function fireNeuron(node, state, currentTime) { try { if (!node || !node.data) return; const data = node.data(); if (!data) return; data.lastFired = currentTime; try { node.style({ 'background-opacity': 1, 'border-width': 3 }); setTimeout(() => { try { node.style({ 'background-opacity': 0.9, 'border-width': 2 }); } catch (e) { // Ignoriere Fehler beim Styling, da der Node möglicherweise nicht mehr existiert } }, 200); } catch (e) { // Ignoriere Styling-Fehler } if (state) { propagateSignal(node, currentTime); } } catch (e) { // Ignoriere Fehler für einzelne Nodes } } function propagateSignal(sourceNode, currentTime) { try { if (!sourceNode || !sourceNode.connectedEdges) return; const outgoingEdges = sourceNode.connectedEdges(); outgoingEdges.forEach(edge => { try { const targetNode = edge.target(); const edgeData = edge.data(); if (!targetNode || !edgeData) return; const latency = edgeData.latency || 100; try { edge.style({ 'line-opacity': 0.8, 'width': edgeData.strength * 3 }); setTimeout(() => { try { edge.style({ 'line-opacity': edgeData.strength * 0.6, 'width': edgeData.strength * 2 }); } catch (e) { // Ignoriere Styling-Fehler } }, 200); } catch (e) { // Ignoriere Styling-Fehler } setTimeout(() => { try { if (!targetNode || !targetNode.data) return; const targetData = targetNode.data(); if (!targetData) return; const timeSinceLastFire = currentTime - (targetData.lastFired || 0); if (timeSinceLastFire > targetData.refractionPeriod) { const signalStrength = edgeData.strength * edgeData.conductionVelocity * sourceNode.data('neuronActivity'); if (signalStrength > targetData.threshold) { fireNeuron(targetNode, true, currentTime + latency); } } } catch (e) { // Ignoriere Fehler für einzelne Nodes } }, latency); } catch (e) { // Ignoriere Fehler für einzelne Edges } }); } catch (e) { // Ignoriere Fehler für einzelne Nodes } } // Starte das Intervall und speichere die Referenz const intervalId = setInterval(simulateNeuralActivity, 100); window.neuralIntervals.push(intervalId); // Rückgabefunktion zum manuellen Stoppen return function stopSimulation() { clearInterval(intervalId); const index = window.neuralIntervals.indexOf(intervalId); if (index > -1) { window.neuralIntervals.splice(index, 1); } }; } // Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises function showFlash(message, type = 'info') { const flashContainer = createFlashContainer(); const flash = document.createElement('div'); flash.className = `flash-message ${type}`; flash.textContent = message; flashContainer.appendChild(flash); document.body.appendChild(flashContainer); setTimeout(() => { flash.classList.add('show'); setTimeout(() => { flash.classList.remove('show'); setTimeout(() => { flashContainer.remove(); }, 300); }, 3000); }, 100); } /** * Zeigt eine Benachrichtigung in der UI an * @param {string|object} message - Die anzuzeigende Nachricht oder ein Fehlerobjekt * @param {string} type - Der Typ der Benachrichtigung ('info', 'success', 'warning', 'error') * @param {number} duration - Die Anzeigedauer in Millisekunden (Standard: 3000) */ function showUINotification(message, type = 'info', duration = 3000) { // Container erstellen, falls er nicht existiert let container = document.getElementById('notification-container'); if (!container) { container = document.createElement('div'); container.id = 'notification-container'; container.style.position = 'fixed'; container.style.top = '1rem'; container.style.right = '1rem'; container.style.zIndex = '1000'; container.style.maxWidth = '400px'; document.body.appendChild(container); } // Benachrichtigung erstellen const notification = document.createElement('div'); notification.className = `notification notification-${type}`; notification.style.padding = '1rem'; notification.style.marginBottom = '0.5rem'; notification.style.borderRadius = '0.25rem'; notification.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)'; notification.style.position = 'relative'; notification.style.opacity = '0'; notification.style.transform = 'translateY(-20px)'; notification.style.transition = 'all 0.3s ease-in-out'; // Farben nach Typ if (type === 'success') { notification.style.backgroundColor = '#059669'; notification.style.color = '#ffffff'; } else if (type === 'error') { notification.style.backgroundColor = '#DC2626'; notification.style.color = '#ffffff'; } else if (type === 'warning') { notification.style.backgroundColor = '#F59E0B'; notification.style.color = '#ffffff'; } else { notification.style.backgroundColor = '#3B82F6'; notification.style.color = '#ffffff'; } // Nachrichteninhalt formatieren let content = ''; if (typeof message === 'object' && message !== null) { // Wenn es ein Fehler-Objekt ist if (message.error) { content = message.error; if (message.details) { content += `
${message.details}`; } } else { // Versuche, das Objekt zu stringifizieren try { content = JSON.stringify(message); } catch (e) { content = 'Objekt konnte nicht angezeigt werden'; } } } else { // String oder andere primitive Typen content = message; } notification.innerHTML = content; // Schließen-Button const closeButton = document.createElement('span'); closeButton.innerHTML = '×'; closeButton.style.position = 'absolute'; closeButton.style.top = '0.25rem'; closeButton.style.right = '0.5rem'; closeButton.style.fontSize = '1.25rem'; closeButton.style.cursor = 'pointer'; closeButton.onclick = () => { notification.style.opacity = '0'; notification.style.transform = 'translateY(-20px)'; setTimeout(() => { if (notification.parentNode === container) { container.removeChild(notification); } }, 300); }; notification.appendChild(closeButton); // Zur Seite hinzufügen container.appendChild(notification); // Animation starten setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateY(0)'; }, 10); // Automatisch ausblenden, wenn keine Dauer von 0 übergeben wurde if (duration > 0) { setTimeout(() => { if (notification.parentNode === container) { notification.style.opacity = '0'; notification.style.transform = 'translateY(-20px)'; setTimeout(() => { if (notification.parentNode === container) { container.removeChild(notification); } }, 300); } }, duration); } } // Funktion zum Anzeigen der Bearbeitungssteuerungen function showEditingControls(nodeId) { const container = nodeId ? document.getElementById(`cy-${nodeId}`).parentElement : document.getElementById('cy').parentElement; if (!container) return; // Erstelle Bearbeitungswerkzeuge, wenn sie noch nicht existieren let editingControls = container.querySelector('.editing-controls'); if (!editingControls) { editingControls = document.createElement('div'); editingControls.className = 'editing-controls'; editingControls.innerHTML = `
`; container.appendChild(editingControls); // Event-Listener für die Bearbeitungswerkzeuge hinzufügen const toolButtons = editingControls.querySelectorAll('.tool-button'); toolButtons.forEach(button => { button.addEventListener('click', function() { const action = this.getAttribute('data-action'); handleEditingAction(action, nodeId); }); }); } // Zeige die Steuerungen an editingControls.style.display = 'block'; } // Funktion zum Ausblenden der Bearbeitungssteuerungen function hideEditingControls(nodeId) { const container = nodeId ? document.getElementById(`cy-${nodeId}`).parentElement : document.getElementById('cy').parentElement; if (!container) return; const editingControls = container.querySelector('.editing-controls'); if (editingControls) { editingControls.style.display = 'none'; } } // Funktion zum Verarbeiten von Bearbeitungsaktionen function handleEditingAction(action, nodeId) { console.log('Bearbeitungsaktion:', action, 'für NodeId:', nodeId); // Finde die relevante Cytoscape-Instanz const cy = nodeId ? (window.subthemeCyInstances && window.subthemeCyInstances[nodeId]) : window.cy; if (!cy) { console.error('Keine Cytoscape-Instanz gefunden'); return; } switch (action) { case 'add-node': addNewNode(cy); break; case 'add-edge': enableEdgeCreationMode(cy); break; case 'delete': deleteSelectedElements(cy); break; case 'save': saveMindmapChanges(cy, nodeId); break; default: console.warn('Unbekannte Aktion:', action); } } // Funktion zum Hinzufügen eines neuen Knotens function addNewNode(cy) { // Zentriere den neuen Knoten im sichtbaren Bereich const extent = cy.extent(); const centerX = (extent.x1 + extent.x2) / 2; const centerY = (extent.y1 + extent.y2) / 2; // Generiere eine eindeutige ID const newId = 'new-node-' + Date.now(); // Füge den neuen Knoten hinzu cy.add({ group: 'nodes', data: { id: newId, label: 'Neuer Knoten', category: 'Wissenschaft', description: 'Beschreibung hinzufügen', hasChildren: false, expanded: false, color: mindmapConfig.categories['Wissenschaft'].color, fontColor: '#ffffff', fontSize: 16, neuronSize: 8, neuronActivity: 0.8 }, position: { x: centerX, y: centerY } }); // Wähle den neuen Knoten aus, um ihn zu bearbeiten const newNode = cy.getElementById(newId); newNode.select(); // Öffne den Bearbeitungsdialog für den neuen Knoten setTimeout(() => { editNodeProperties(newNode); }, 300); } // Funktion zum Bearbeiten von Knoteneigenschaften function editNodeProperties(node) { if (!node) return; // Erstelle den Modal-Hintergrund const backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop'; document.body.appendChild(backdrop); // Erstelle den Bearbeitungsdialog const dialog = document.createElement('div'); dialog.className = 'node-edit-dialog'; // Hole die aktuellen Daten des Knotens const data = node.data(); // Generiere die Kategorie-Optionen let categoryOptions = ''; for (const category in mindmapConfig.categories) { categoryOptions += ``; } // Erstelle das Formular dialog.innerHTML = `

Knoten bearbeiten

`; document.body.appendChild(dialog); // Event-Listener für den Abbrechen-Button dialog.querySelector('button.cancel').addEventListener('click', function() { document.body.removeChild(dialog); document.body.removeChild(backdrop); }); // Event-Listener für den Modal-Hintergrund backdrop.addEventListener('click', function() { document.body.removeChild(dialog); document.body.removeChild(backdrop); }); // Event-Listener für das Formular dialog.querySelector('form').addEventListener('submit', function(e) { e.preventDefault(); // Hole die neuen Werte aus dem Formular const label = document.getElementById('node-label').value; const category = document.getElementById('node-category').value; const description = document.getElementById('node-description').value; const hasChildren = document.getElementById('node-has-children').value === 'true'; // Bestimme die Farbe basierend auf der Kategorie const color = mindmapConfig.categories[category]?.color || data.color || '#60a5fa'; // Aktualisiere die Daten des Knotens node.data({ ...data, label, category, description, hasChildren, color }); // Schließe den Dialog document.body.removeChild(dialog); document.body.removeChild(backdrop); // Benachrichtigung anzeigen showUINotification('Knoten wurde aktualisiert', 'success'); }); } // Funktion zum Anzeigen des Kontext-Menüs für einen Knoten function showNodeContextMenu(node, position) { if (!node) return; // Entferne existierende Kontext-Menüs const existingMenu = document.querySelector('.context-menu'); if (existingMenu) { existingMenu.parentNode.removeChild(existingMenu); } // Erstelle das Kontext-Menü const menu = document.createElement('div'); menu.className = 'context-menu'; menu.style.left = `${position.x}px`; menu.style.top = `${position.y}px`; // Menü-Einträge menu.innerHTML = `
Bearbeiten
Verbindung erstellen
Löschen
`; document.body.appendChild(menu); // Event-Listener für Menü-Einträge menu.querySelectorAll('.context-menu-item').forEach(item => { item.addEventListener('click', function() { const action = this.getAttribute('data-action'); switch (action) { case 'edit': editNodeProperties(node); break; case 'connect': startEdgeCreation(node); break; case 'delete': deleteNode(node); break; } // Entferne das Menü document.body.removeChild(menu); }); }); // Klick außerhalb des Menüs schließt es document.addEventListener('click', function closeMenu(e) { if (!menu.contains(e.target)) { if (document.body.contains(menu)) { document.body.removeChild(menu); } document.removeEventListener('click', closeMenu); } }); } // Funktion zum Anzeigen des Menüs zum Hinzufügen eines Knotens function showAddNodeMenu(position) { // Entferne existierende Kontext-Menüs const existingMenu = document.querySelector('.context-menu'); if (existingMenu) { existingMenu.parentNode.removeChild(existingMenu); } // Erstelle das Kontext-Menü const menu = document.createElement('div'); menu.className = 'context-menu'; menu.style.left = `${position.x}px`; menu.style.top = `${position.y}px`; // Kategorie-Einträge für neue Knoten let categoryItems = ''; for (const category in mindmapConfig.categories) { const categoryConfig = mindmapConfig.categories[category]; categoryItems += `
${category}
`; } // Menü-Einträge menu.innerHTML = `
Neuen Knoten hinzufügen
${categoryItems} `; document.body.appendChild(menu); // Event-Listener für Kategorie-Einträge menu.querySelectorAll('.context-menu-item').forEach(item => { item.addEventListener('click', function() { const category = this.getAttribute('data-category'); // Füge einen neuen Knoten an der Position hinzu addNewNodeAtPosition(cy, position, category); // Entferne das Menü document.body.removeChild(menu); }); }); // Klick außerhalb des Menüs schließt es document.addEventListener('click', function closeMenu(e) { if (!menu.contains(e.target)) { if (document.body.contains(menu)) { document.body.removeChild(menu); } document.removeEventListener('click', closeMenu); } }); } // Funktion zum Hinzufügen eines neuen Knotens an einer bestimmten Position function addNewNodeAtPosition(cy, position, category) { if (!cy) return; // Berechne die Modellposition (statt der gerenderten Position) const modelPosition = cy.renderer().projectIntoViewport(position.x, position.y); // Generiere eine eindeutige ID const newId = 'new-node-' + Date.now(); // Hole die Kategorie-Konfiguration const categoryConfig = mindmapConfig.categories[category] || mindmapConfig.categories['Wissenschaft']; // Füge den neuen Knoten hinzu cy.add({ group: 'nodes', data: { id: newId, label: 'Neuer ' + category + '-Knoten', category: category, description: categoryConfig.description || 'Beschreibung hinzufügen', hasChildren: false, expanded: false, color: categoryConfig.color, fontColor: '#ffffff', fontSize: 16, neuronSize: 8, neuronActivity: 0.8 }, position: { x: modelPosition[0], y: modelPosition[1] } }); // Wähle den neuen Knoten aus, um ihn zu bearbeiten const newNode = cy.getElementById(newId); newNode.select(); // Öffne den Bearbeitungsdialog für den neuen Knoten setTimeout(() => { editNodeProperties(newNode); }, 300); } // Funktion zum Starten der Erstellung einer Kante function startEdgeCreation(sourceNode) { if (!sourceNode) return; const cy = sourceNode.cy(); if (!cy) return; // Markiere den Quellknoten sourceNode.addClass('edge-source'); // Füge dem Container die Klasse für den Kanten-Erstellungsmodus hinzu cy.container().classList.add('edge-creation-mode'); // Zeige Hinweis an showUINotification('Wähle einen Zielknoten für die Verbindung', 'info'); // Einmaliger Event-Listener für das Auswählen des Zielknotens cy.once('tap', 'node', function(evt) { const targetNode = evt.target; // Ignoriere, wenn es der gleiche Knoten ist if (targetNode.id() === sourceNode.id()) { showUINotification('Quell- und Zielknoten dürfen nicht identisch sein', 'warning'); finishEdgeCreation(); return; } // Prüfe, ob bereits eine Kante existiert const existingEdge = cy.edges(`[source="${sourceNode.id()}"][target="${targetNode.id()}"]`); if (existingEdge.length > 0) { showUINotification('Verbindung existiert bereits', 'warning'); finishEdgeCreation(); return; } // Füge die neue Kante hinzu cy.add({ group: 'edges', data: { source: sourceNode.id(), target: targetNode.id(), strength: 0.5, conductionVelocity: Math.random() * 0.5 + 0.3, latency: Math.random() * 100 + 50 } }); showUINotification('Verbindung wurde erstellt', 'success'); finishEdgeCreation(); }); // Event-Listener für Abbruch durch Klick auf leeren Bereich cy.once('tap', function(evt) { if (evt.target === cy) { showUINotification('Kantenerstellung abgebrochen', 'info'); finishEdgeCreation(); } }); function finishEdgeCreation() { sourceNode.removeClass('edge-source'); cy.container().classList.remove('edge-creation-mode'); } } // Funktion zum Aktivieren des Kantenerstellungsmodus function enableEdgeCreationMode(cy) { if (!cy) return; // Füge dem Container die Klasse für den Kanten-Erstellungsmodus hinzu cy.container().classList.add('edge-creation-mode'); // Zeige Hinweis an showUINotification('Wähle einen Quellknoten für die Verbindung', 'info'); // Einmaliger Event-Listener für das Auswählen des Quellknotens cy.once('tap', 'node', function(evt) { const sourceNode = evt.target; startEdgeCreation(sourceNode); }); // Event-Listener für Abbruch durch Rechtsklick oder Escape-Taste const cancelEdgeCreation = function() { cy.container().classList.remove('edge-creation-mode'); cy.removeListener('tap'); showUINotification('Kantenerstellung abgebrochen', 'info'); }; cy.once('cxttap', cancelEdgeCreation); document.addEventListener('keydown', function escKeyHandler(e) { if (e.key === 'Escape') { cancelEdgeCreation(); document.removeEventListener('keydown', escKeyHandler); } }); } // Funktion zum Löschen eines Knotens function deleteNode(node) { if (!node) return; // Frage den Benutzer, ob er den Knoten wirklich löschen möchte if (confirm(`Möchtest du den Knoten "${node.data('label')}" wirklich löschen?`)) { // Entferne den Knoten und alle zugehörigen Kanten node.remove(); showUINotification('Knoten wurde gelöscht', 'success'); } } // Funktion zum Löschen ausgewählter Elemente function deleteSelectedElements(cy) { if (!cy) return; const selectedElements = cy.elements(':selected'); if (selectedElements.length === 0) { showUINotification('Keine Elemente ausgewählt', 'warning'); return; } // Zähle die ausgewählten Knoten und Kanten const selectedNodes = selectedElements.filter('node'); const selectedEdges = selectedElements.filter('edge'); let confirmMessage = 'Möchtest du die ausgewählten Elemente wirklich löschen?'; if (selectedNodes.length > 0 && selectedEdges.length > 0) { confirmMessage = `Möchtest du ${selectedNodes.length} Knoten und ${selectedEdges.length} Verbindungen wirklich löschen?`; } else if (selectedNodes.length > 0) { confirmMessage = `Möchtest du ${selectedNodes.length} Knoten wirklich löschen?`; } else if (selectedEdges.length > 0) { confirmMessage = `Möchtest du ${selectedEdges.length} Verbindungen wirklich löschen?`; } // Frage den Benutzer, ob er die ausgewählten Elemente wirklich löschen möchte if (confirm(confirmMessage)) { // Entferne die ausgewählten Elemente selectedElements.remove(); let successMessage = 'Elemente wurden gelöscht'; if (selectedNodes.length > 0 && selectedEdges.length > 0) { successMessage = `${selectedNodes.length} Knoten und ${selectedEdges.length} Verbindungen wurden gelöscht`; } else if (selectedNodes.length > 0) { successMessage = `${selectedNodes.length} Knoten wurden gelöscht`; } else if (selectedEdges.length > 0) { successMessage = `${selectedEdges.length} Verbindungen wurden gelöscht`; } showUINotification(successMessage, 'success'); } } // Funktion zum Speichern der Änderungen an der Mindmap function saveMindmapChanges(cy, nodeId) { if (!cy) return; // Sammle die Daten aller Knoten und Kanten const nodes = []; const edges = []; cy.nodes().forEach(node => { const data = node.data(); const position = node.position(); nodes.push({ id: data.id, name: data.label, category: data.category, description: data.description, has_children: data.hasChildren, color_code: data.color, position_x: position.x, position_y: position.y }); }); cy.edges().forEach(edge => { const data = edge.data(); edges.push({ source: data.source, target: data.target, strength: data.strength }); }); // Erstelle die Daten für den API-Aufruf const saveData = { nodes: nodes, edges: edges, parent_id: nodeId || null }; console.log('Speichere Mindmap-Änderungen:', saveData); // Zeige eine Speicherbenachrichtigung an showUINotification('Speichere Mindmap-Änderungen...', 'info'); // Sende die Daten an den Server fetch('/api/mindmap/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(saveData) }) .then(response => { if (!response.ok) { return response.json().then(data => { throw new Error(data.error || `Fehler ${response.status}: ${response.statusText}`); }); } return response.json(); }) .then(data => { console.log('Speichern erfolgreich:', data); showUINotification('Mindmap wurde erfolgreich gespeichert', 'success'); }) .catch(error => { console.error('Fehler beim Speichern der Mindmap:', error); showUINotification({ error: 'Fehler beim Speichern der Mindmap', details: error.message }, 'error'); }); } // Füge CSS-Stile für den Bearbeitungsmodus hinzu const editingStyles = document.createElement('style'); editingStyles.textContent = ` /* Bearbeitungsmodus-Cursor */ .editing-mode { cursor: grab !important; } .editing-mode:active { cursor: grabbing !important; } /* Bearbeitungssteuerungen */ .editing-controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 1010; display: none; } .editing-toolbar { display: flex; background: rgba(30, 41, 59, 0.85); border-radius: 8px; padding: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.1); } .tool-button { background: rgba(255, 255, 255, 0.1); border: none; color: white; width: 40px; height: 40px; border-radius: 6px; margin: 0 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .tool-button:hover { background: rgba(255, 255, 255, 0.2); transform: translateY(-2px); } .tool-button:active { transform: translateY(0); } .tool-button[data-action="add-node"] { background: rgba(16, 185, 129, 0.2); } .tool-button[data-action="add-edge"] { background: rgba(59, 130, 246, 0.2); } .tool-button[data-action="delete"] { background: rgba(239, 68, 68, 0.2); } .tool-button[data-action="save"] { background: rgba(245, 158, 11, 0.2); } /* Node-Bearbeitungsdialog */ .node-edit-dialog { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(30, 41, 59, 0.95); border-radius: 12px; padding: 20px; width: 400px; max-width: 90vw; z-index: 2000; box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.1); } .node-edit-dialog h3 { margin-top: 0; color: white; border-bottom: 1px solid rgba(255, 255, 255, 0.1); padding-bottom: 10px; } .form-group { margin-bottom: 16px; } .form-group label { display: block; margin-bottom: 6px; color: #e2e8f0; font-size: 0.9rem; } .form-group input, .form-group textarea, .form-group select { width: 100%; padding: 8px 12px; background: rgba(15, 23, 42, 0.5); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 6px; color: white; font-size: 0.95rem; } .form-group input:focus, .form-group textarea:focus, .form-group select:focus { outline: none; border-color: #8b5cf6; box-shadow: 0 0 0 2px rgba(139, 92, 246, 0.2); } .form-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; } .form-actions button { padding: 8px 16px; border-radius: 6px; border: none; font-weight: 500; cursor: pointer; transition: all 0.2s ease; } .form-actions button.save { background: #8b5cf6; color: white; } .form-actions button.cancel { background: rgba(255, 255, 255, 0.1); color: white; } .form-actions button:hover { transform: translateY(-2px); } /* Kontext-Menü */ .context-menu { position: absolute; background: rgba(30, 41, 59, 0.95); border-radius: 8px; padding: 8px 0; min-width: 160px; z-index: 2000; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.1); } .context-menu-item { padding: 8px 16px; color: white; cursor: pointer; transition: background-color 0.2s ease; display: flex; align-items: center; gap: 8px; } .context-menu-item:hover { background: rgba(255, 255, 255, 0.1); } .context-menu-item svg { width: 16px; height: 16px; } /* Modal-Hintergrund */ .modal-backdrop { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 1999; backdrop-filter: blur(2px); } /* Edge-Erstellungsmodus */ .edge-creation-mode { cursor: crosshair !important; } /* Erweiterte Mindmap-Seite */ .mindmap-header { display: flex; align-items: center; justify-content: space-between; } .mindmap-actions { display: flex; gap: 10px; } .edit-button { display: flex; align-items: center; gap: 6px; background: rgba(139, 92, 246, 0.2); border: none; color: white; padding: 6px 12px; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; } .edit-button:hover { background: rgba(139, 92, 246, 0.3); transform: translateY(-2px); } .edit-button svg { opacity: 0.8; } `; document.head.appendChild(editingStyles); // CSS-Styles für die Unterkategorien-Seite (function() { const style = document.createElement('style'); style.textContent = ` .mindmap-subpage { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1000; display: flex; flex-direction: column; background: var(--bg-primary, linear-gradient(135deg, #1a1f2e 0%, #0f172a 100%)); } .mindmap-subpage .subpage-header { display: flex; align-items: center; gap: 12px; padding: 16px; background: rgba(15, 23, 42, 0.9); backdrop-filter: blur(10px); border-bottom: 1px solid rgba(255, 255, 255, 0.1); z-index: 10; } .mindmap-subpage .back-button { background: rgba(255, 255, 255, 0.1); border: none; color: white; width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } .mindmap-subpage .back-button:hover { background: rgba(255, 255, 255, 0.2); transform: translateY(-2px); } .mindmap-subpage .subpage-title { font-size: 1.5rem; font-weight: 600; color: white; margin: 0; background: linear-gradient(90deg, #60a5fa, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .mindmap-subpage .subpage-cy-container { flex: 1; width: 100%; height: calc(100% - 72px); position: relative; } .mindmap-subpage .mindmap-toolbar { position: absolute; top: 20px; left: 50%; transform: translateX(-50%); display: flex; gap: 8px; padding: 8px; background: rgba(30, 41, 59, 0.8); border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); z-index: 10; backdrop-filter: blur(8px); border: 1px solid rgba(255, 255, 255, 0.1); } .mindmap-subpage .mindmap-toolbar button { width: 40px; height: 40px; border: none; background: rgba(255, 255, 255, 0.1); color: white; border-radius: 6px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.2s ease; } .mindmap-subpage .mindmap-toolbar button:hover { background: rgba(139, 92, 246, 0.5); transform: translateY(-2px); } .mindmap-subpage .mindmap-toolbar button i { font-size: 16px; } .dark .mindmap-subpage { background: linear-gradient(135deg, #0f172a 0%, #0c1221 100%); } `; document.head.appendChild(style); })(); // Funktion zum Laden der Subthemen async function loadSubthemes(node) { try { if (!node || !node.id) { console.error('Ungültiger Knoten für Subthemen'); return; } const nodeId = node.id(); if (!nodeId) { console.error('Ungültige Knoten-ID für Subthemen'); return; } // Prüfe ob die Haupt-Cytoscape-Instanz noch gültig ist if (!window.cy || typeof window.cy.container !== 'function') { console.error('Haupt-Cytoscape-Instanz nicht mehr gültig'); showUINotification('Fehler: Mindmap-Instanz nicht mehr verfügbar', 'error'); return; } const mindmapData = await loadMindmapData(nodeId); if (!mindmapData || !mindmapData.nodes || !mindmapData.edges) { console.warn('Keine gültigen Daten für Subthemen gefunden'); showUINotification('Keine Unterthemen verfügbar', 'info'); return; } showUINotification('Lade Subthemen...', 'info'); // Finde den Container const mindmapContainer = document.querySelector('.mindmap-container'); if (!mindmapContainer) { console.error('Mindmap-Container nicht gefunden'); showUINotification('Fehler: Mindmap-Container nicht gefunden', 'error'); return; } // Verstecke die Hauptmindmap nur wenn sie noch gültig ist try { const mainContainer = window.cy.container(); if (mainContainer && mainContainer.parentNode) { mainContainer.style.display = 'none'; } else { console.warn('Haupt-Cytoscape-Container nicht mehr gültig'); } } catch (e) { console.error('Fehler beim Verstecken der Hauptmindmap:', e); } // Erstelle eine neue Seite für die Unterkategorien const subpage = document.createElement('div'); subpage.className = 'mindmap-subpage'; subpage.setAttribute('data-parent-id', nodeId); // Erstelle den Header mit Zurück-Button und Titel const header = document.createElement('div'); header.className = 'subpage-header'; header.innerHTML = `

${node.data('label') || node.data('name') || 'Unterkategorie'}

`; // Erstelle den Container für die Cytoscape-Instanz const cyContainer = document.createElement('div'); cyContainer.className = 'subpage-cy-container'; cyContainer.id = `cy-${nodeId}`; // Erstelle die Toolbar const toolbar = document.createElement('div'); toolbar.className = 'mindmap-toolbar'; toolbar.innerHTML = ` `; // Füge alle Elemente zum DOM hinzu cyContainer.appendChild(toolbar); subpage.appendChild(header); subpage.appendChild(cyContainer); mindmapContainer.appendChild(subpage); // Warte, bis der DOM aktualisiert ist await new Promise(resolve => setTimeout(resolve, 10)); // Erstelle ein Array mit den Knoten-Elementen const elements = []; // Knoten mit angepassten Daten für neuronale Eigenschaften mindmapData.nodes.forEach(node => { try { const isCenter = node.is_center || false; const category = node.category || 'Wissenschaft'; // Fallback-Kategorie const categoryConfig = mindmapConfig.categories[category] || mindmapConfig.categories['Wissenschaft']; elements.push({ group: 'nodes', data: { id: node.id, label: node.name, category: category, description: node.description, hasChildren: node.has_children, expanded: false, color: node.color_code || categoryConfig.color, fontColor: '#ffffff', fontSize: isCenter ? mindmapConfig.centerNodeStyle.fontSize : mindmapConfig.defaultNodeStyle.fontSize, isCenter: isCenter, neuronSize: isCenter ? mindmapConfig.centerNodeStyle.neuronSize : mindmapConfig.defaultNodeStyle.neuronSize, neuronActivity: isCenter ? mindmapConfig.centerNodeStyle.neuronActivity : mindmapConfig.defaultNodeStyle.neuronActivity, refractionPeriod: Math.random() * 300 + 700, threshold: Math.random() * 0.3 + 0.6, lastFired: 0 } }); } catch (e) { console.error('Fehler beim Verarbeiten eines Knotens:', e, node); } }); // Kanten mit angepassten Daten für neuronale Eigenschaften mindmapData.edges.forEach(edge => { try { elements.push({ group: 'edges', data: { source: edge.source, target: edge.target, strength: edge.strength || 0.5, conductionVelocity: Math.random() * 0.5 + 0.3, latency: Math.random() * 100 + 50 } }); } catch (e) { console.error('Fehler beim Verarbeiten einer Kante:', e, edge); } }); // Finde den DOM-Container const container = document.getElementById(`cy-${nodeId}`); if (!container) { throw new Error(`Container für Cytoscape-Instanz nicht gefunden: cy-${nodeId}`); } // Initialisiere subthemeCyInstances wenn noch nicht vorhanden if (!window.subthemeCyInstances) { window.subthemeCyInstances = {}; } // Zerstöre alte Instanz falls vorhanden if (window.subthemeCyInstances[nodeId]) { try { const oldInstance = window.subthemeCyInstances[nodeId]; if (oldInstance && typeof oldInstance.destroy === 'function') { oldInstance.destroy(); } } catch (e) { console.error('Fehler beim Zerstören der alten Instanz:', e); } } // Erstelle die neue Cytoscape-Instanz const newCy = cytoscape({ container: container, elements: elements, style: [ { selector: 'node', style: mindmapStyles.node.base }, { selector: 'node[isCenter = true]', style: mindmapStyles.node.center }, { selector: 'edge', style: mindmapStyles.edge.base }, { selector: '.selected', style: mindmapStyles.node.selected } ], layout: mindmapStyles.layout.base, wheelSensitivity: 0, minZoom: 0.2, maxZoom: 2.5, headless: false, styleEnabled: true, hideEdgesOnViewport: true, textureOnViewport: true, motionBlur: false, motionBlurOpacity: 0.2, pixelRatio: 'auto' }); // Speichere die Instanz nur wenn sie erfolgreich erstellt wurde if (newCy && typeof newCy.container === 'function') { window.subthemeCyInstances[nodeId] = newCy; } else { throw new Error('Cytoscape-Instanz konnte nicht korrekt initialisiert werden'); } // Rest der Funktion bleibt gleich... // ... existing code ... } catch (error) { console.error('Fehler beim Laden der Subthemen:', error); showUINotification('Fehler beim Laden der Subthemen: ' + error.message, 'error'); // Versuche die Hauptmindmap wieder anzuzeigen try { if (window.cy && typeof window.cy.container === 'function') { const mainContainer = window.cy.container(); if (mainContainer && mainContainer.parentNode) { mainContainer.style.display = 'block'; } } } catch (e) { console.error('Fehler beim Wiederherstellen der Hauptmindmap:', e); } } } // Funktion zum Zurücknavigieren function goBack() { try { console.log('goBack Funktion aufgerufen'); const subpage = document.querySelector('.mindmap-subpage'); if (!subpage) { console.warn('Keine Unterseite gefunden'); return; } console.log('Unterseite gefunden:', subpage); // Entferne erst alle Event-Listener und zerstöre die Cytoscape-Instanz der Subpage const subpageId = subpage.querySelector('.subpage-cy-container')?.id; if (subpageId) { const subthemeId = subpageId.replace('cy-', ''); if (window.subthemeCyInstances && window.subthemeCyInstances[subthemeId]) { try { // Entferne alle Event-Listener der Subtheme-Instanz const cyInstance = window.subthemeCyInstances[subthemeId]; if (cyInstance && typeof cyInstance.removeAllListeners === 'function') { cyInstance.removeAllListeners(); } // Zerstöre die Cytoscape-Instanz nur wenn sie noch gültig ist if (cyInstance && typeof cyInstance.destroy === 'function') { cyInstance.destroy(); } // Entferne die Referenz delete window.subthemeCyInstances[subthemeId]; console.log('Subtheme Cytoscape-Instanz zerstört:', subthemeId); } catch (e) { console.error('Fehler beim Aufräumen der Subtheme-Instanz:', e); } } } // Entferne die Unterseite mit Animation subpage.style.opacity = '0'; subpage.style.transform = 'translateY(20px)'; setTimeout(() => { try { // Entferne die Unterseite aus dem DOM if (subpage.parentNode) { subpage.parentNode.removeChild(subpage); } // Stelle sicher, dass die Haupt-Cytoscape-Instanz existiert und gültig ist if (window.cy && typeof window.cy.container === 'function') { const container = window.cy.container(); if (container && container.parentNode) { console.log('Zeige Haupt-Cytoscape-Container an'); container.style.display = 'block'; // Aktualisiere das Layout nur wenn die Instanz noch gültig ist setTimeout(() => { try { if (window.cy && typeof window.cy.resize === 'function' && typeof window.cy.fit === 'function') { window.cy.resize(); window.cy.fit(); } } catch (e) { console.error('Fehler beim Aktualisieren des Layouts:', e); } }, 100); } else { console.warn('Haupt-Cytoscape-Container nicht mehr gültig'); initializeMindmap().catch(console.error); } } else { console.warn('Haupt-Cytoscape-Instanz nicht mehr gültig'); initializeMindmap().catch(console.error); } } catch (e) { console.error('Fehler beim Aufräumen:', e); showUINotification('Fehler beim Zurücknavigieren: ' + e.message, 'error'); } }, 200); } catch (error) { console.error('Fehler in goBack Funktion:', error); showUINotification('Fehler beim Zurücknavigieren: ' + error.message, 'error'); } } // Funktion global verfügbar machen window.goBack = goBack; // Verbesserte Zoom-Funktionalität für die Hauptmindmap document.addEventListener('DOMContentLoaded', function() { // Warte bis die Mindmap geladen ist document.addEventListener('mindmap-loaded', function() { // Zoom-Buttons für die Hauptmindmap const zoomInBtn = document.getElementById('zoomIn'); const zoomOutBtn = document.getElementById('zoomOut'); const resetViewBtn = document.getElementById('resetView'); if (zoomInBtn) { zoomInBtn.addEventListener('click', function() { if (!window.cy) return; window.cy.zoom({ level: window.cy.zoom() * 1.3, renderedPosition: { x: window.cy.width() / 2, y: window.cy.height() / 2 } }); }); } if (zoomOutBtn) { zoomOutBtn.addEventListener('click', function() { if (!window.cy) return; window.cy.zoom({ level: window.cy.zoom() / 1.3, renderedPosition: { x: window.cy.width() / 2, y: window.cy.height() / 2 } }); }); } if (resetViewBtn) { resetViewBtn.addEventListener('click', function() { if (!window.cy) return; // Auswahl zurücksetzen window.cy.nodes().forEach(node => { node.removeStyle(); node.connectedEdges().removeStyle(); }); // Zoom auf eine angenehme Stufe setzen und zentrieren window.cy.zoom({ level: 0.7, renderedPosition: { x: window.cy.width() / 2, y: window.cy.height() / 2 } }); window.cy.center(); }); } }); }); // Funktionen für Knoteninfo-Panel function showNodeInfo(node) { if (!node || !node.isNode()) return; const infoPanel = document.getElementById('infoPanel'); if (!infoPanel) return; // Stelle sicher, dass das Info-Panel die notwendigen Elemente enthält if (!infoPanel.querySelector('.info-title')) { infoPanel.innerHTML = `

`; } const infoTitle = infoPanel.querySelector('.info-title'); const infoContent = infoPanel.querySelector('.info-content'); const data = node.data(); infoTitle.textContent = data.label || 'Knotendetails'; let contentHTML = `

Kategorie: ${data.category || 'Nicht kategorisiert'}

Beschreibung: ${data.description || 'Keine Beschreibung verfügbar'}

`; if (data.hasChildren || data.has_children) { contentHTML += `

Hat Unterknoten

`; } infoContent.innerHTML = contentHTML; infoPanel.classList.add('visible'); } function hideNodeInfo() { const infoPanel = document.getElementById('infoPanel'); if (infoPanel) { infoPanel.classList.remove('visible'); } }