diff --git a/static/js/mindmap_controls.js b/static/js/mindmap_controls.js new file mode 100644 index 0000000..bf3271a --- /dev/null +++ b/static/js/mindmap_controls.js @@ -0,0 +1,220 @@ +/** + * Systades Mindmap Steuerung + * Implementierung der Funktionalitäten für die Mindmap-Kontrollbuttons + */ + +document.addEventListener('DOMContentLoaded', function() { + console.log('Mindmap-Steuerung wird initialisiert...'); + + // Vergrößern-Funktion (Kontrollpanel) + const zoomInBtn = document.querySelector('#zoomIn'); + if (zoomInBtn) { + console.log('Zoom-In Button gefunden'); + zoomInBtn.addEventListener('click', function() { + vergrößern(); + }); + } + + // Verkleinern-Funktion (Kontrollpanel) + const zoomOutBtn = document.querySelector('#zoomOut'); + if (zoomOutBtn) { + console.log('Zoom-Out Button gefunden'); + zoomOutBtn.addEventListener('click', function() { + verkleinern(); + }); + } + + // Zurücksetzen-Funktion (Kontrollpanel) + const resetViewBtn = document.querySelector('#resetView'); + if (resetViewBtn) { + console.log('Reset-View Button gefunden'); + resetViewBtn.addEventListener('click', function() { + zurücksetzen(); + }); + } + + // Legende-Funktion (Kontrollpanel) + const toggleLegendBtn = document.querySelector('#toggleLegend'); + if (toggleLegendBtn) { + console.log('Toggle-Legend Button gefunden'); + toggleLegendBtn.addEventListener('click', function() { + legendeUmschalten(); + }); + } + + // Seitliche Buttons + const vergrößernBtn = document.querySelector('#vergrößernBtn'); + if (vergrößernBtn) { + console.log('Vergrößern Button (Seitenleiste) gefunden'); + vergrößernBtn.addEventListener('click', function() { + vergrößern(); + animateButtonClick(this); + }); + } else { + console.warn('Vergrößern Button nicht gefunden'); + } + + const verkleinernBtn = document.querySelector('#verkleinernBtn'); + if (verkleinernBtn) { + console.log('Verkleinern Button (Seitenleiste) gefunden'); + verkleinernBtn.addEventListener('click', function() { + verkleinern(); + animateButtonClick(this); + }); + } else { + console.warn('Verkleinern Button nicht gefunden'); + } + + const zurücksetzenBtn = document.querySelector('#zurücksetzenBtn'); + if (zurücksetzenBtn) { + console.log('Zurücksetzen Button (Seitenleiste) gefunden'); + zurücksetzenBtn.addEventListener('click', function() { + zurücksetzen(); + animateButtonClick(this); + }); + } else { + console.warn('Zurücksetzen Button nicht gefunden'); + } +}); + +/** + * Vergrößert die aktuelle Mindmap-Ansicht + */ +function vergrößern() { + console.log('Vergrößern-Funktion aufgerufen'); + if (window.cy) { + const aktuellerZoom = window.cy.zoom(); + window.cy.zoom({ + level: aktuellerZoom * 1.2, + renderedPosition: { x: window.innerWidth / 2, y: window.innerHeight / 2 } + }); + console.log('Vergrößerung: Neuer Zoom-Level:', window.cy.zoom()); + } else { + console.error('Cytoscape-Instanz nicht verfügbar'); + } +} + +/** + * Verkleinert die aktuelle Mindmap-Ansicht + */ +function verkleinern() { + console.log('Verkleinern-Funktion aufgerufen'); + if (window.cy) { + const aktuellerZoom = window.cy.zoom(); + window.cy.zoom({ + level: aktuellerZoom * 0.8, + renderedPosition: { x: window.innerWidth / 2, y: window.innerHeight / 2 } + }); + console.log('Verkleinerung: Neuer Zoom-Level:', window.cy.zoom()); + } else { + console.error('Cytoscape-Instanz nicht verfügbar'); + } +} + +/** + * Setzt die Mindmap-Ansicht zurück, sodass alle Elemente sichtbar sind + */ +function zurücksetzen() { + console.log('Zurücksetzen-Funktion aufgerufen'); + if (window.cy) { + window.cy.fit(); + window.cy.center(); + console.log('Ansicht zurückgesetzt'); + + // Zeige kurze Bestätigung an + zeigeFlashNachricht('Ansicht zurückgesetzt', 'info'); + } else { + console.error('Cytoscape-Instanz nicht verfügbar'); + } +} + +/** + * Schaltet die Anzeige der Kategorie-Legende um + */ +function legendeUmschalten() { + console.log('Legende-Umschalten-Funktion aufgerufen'); + const legende = document.getElementById('categoryLegend'); + if (legende) { + const neuerZustand = legende.style.display === 'none' || legende.style.display === '' ? 'flex' : 'none'; + legende.style.display = neuerZustand; + console.log('Legende ist jetzt:', neuerZustand === 'flex' ? 'sichtbar' : 'ausgeblendet'); + + // Zeige kurze Bestätigung an + zeigeFlashNachricht( + neuerZustand === 'flex' ? 'Legende wird angezeigt' : 'Legende ausgeblendet', + 'info' + ); + } else { + console.error('Legende-Element nicht gefunden'); + } +} + +/** + * Zeigt eine kurze Flash-Nachricht auf dem Bildschirm an + * @param {string} nachricht - Die anzuzeigende Nachricht + * @param {string} typ - Der Typ der Nachricht (info, success, warning, error) + */ +function zeigeFlashNachricht(nachricht, typ = 'info') { + console.log(`Flash-Nachricht: ${nachricht} (${typ})`); + + // Prüfe, ob bereits eine Flash-Nachricht existiert + let flashElement = document.getElementById('flash-nachricht'); + + // Falls nicht, erstelle sie + if (!flashElement) { + flashElement = document.createElement('div'); + flashElement.id = 'flash-nachricht'; + document.body.appendChild(flashElement); + + // Styles für das Flash-Element + Object.assign(flashElement.style, { + position: 'fixed', + bottom: '20px', + left: '50%', + transform: 'translateX(-50%)', + padding: '10px 20px', + borderRadius: '5px', + color: 'white', + fontWeight: 'bold', + zIndex: '9999', + opacity: '0', + transition: 'opacity 0.3s ease-in-out' + }); + } + + // Setze Hintergrundfarbe je nach Typ + const hintergrundFarben = { + info: 'rgba(59, 130, 246, 0.9)', + success: 'rgba(16, 185, 129, 0.9)', + warning: 'rgba(245, 158, 11, 0.9)', + error: 'rgba(220, 38, 38, 0.9)' + }; + + flashElement.style.backgroundColor = hintergrundFarben[typ] || hintergrundFarben.info; + flashElement.textContent = nachricht; + + // Animiere das Einblenden + flashElement.style.opacity = '1'; + + // Ausblenden nach 2 Sekunden + setTimeout(() => { + flashElement.style.opacity = '0'; + }, 2000); +} + +/** + * Animiert einen Button beim Klicken + * @param {HTMLElement} button - Das Button-Element, das animiert werden soll + */ +function animateButtonClick(button) { + // Aktuelle Transformation speichern + const originalTransform = button.style.transform; + + // Animation anwenden + button.style.transform = 'scale(0.95)'; + + // Nach kurzer Zeit zurücksetzen + setTimeout(() => { + button.style.transform = originalTransform; + }, 150); +} \ No newline at end of file diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js index 2541013..26c88a5 100644 --- a/static/js/update_mindmap.js +++ b/static/js/update_mindmap.js @@ -1,195 +1,58 @@ /** - * 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 + * Mindmap-Funktionalitäten + * Diese Datei enthält die grundlegenden Funktionen für die Mindmap-Visualisierung */ -// 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' - } -}; +// Globale Variable für das Cytoscape-Objekt +window.cy = null; -// 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; - } -} - -// Funktion zum Initialisieren der Mindmap +/** + * Initialisiert die Mindmap mit Standarddaten + */ async function initializeMindmap() { + console.log('Initialisiere Mindmap...'); + + // Beispieldaten für die Mindmap (falls keine API-Daten verfügbar sind) + const fallbackData = { + nodes: [ + { id: 'wissen', name: 'Wissen', category: 'Zentral', description: 'Zentrum der Wissensdatenbank', has_children: true, is_center: true, color_code: '#f5f5f5' }, + { id: 'philosophie', name: 'Philosophie', category: 'Philosophie', description: 'Philosophisches Denken', has_children: true, is_center: false, color_code: '#9F7AEA' }, + { id: 'wissenschaft', name: 'Wissenschaft', category: 'Wissenschaft', description: 'Wissenschaftliche Erkenntnisse', has_children: true, is_center: false, color_code: '#60A5FA' }, + { id: 'technologie', name: 'Technologie', category: 'Technologie', description: 'Technologische Entwicklungen', has_children: true, is_center: false, color_code: '#10B981' }, + { id: 'kunst', name: 'Künste', category: 'Künste', description: 'Kreative Ausdrucksformen', has_children: true, is_center: false, color_code: '#F59E0B' }, + { id: 'psychologie', name: 'Psychologie', category: 'Psychologie', description: 'Menschliches Verhalten und Geist', has_children: true, is_center: false, color_code: '#EF4444' } + ], + edges: [ + { source: 'wissen', target: 'philosophie', strength: 0.8 }, + { source: 'wissen', target: 'wissenschaft', strength: 0.8 }, + { source: 'wissen', target: 'technologie', strength: 0.7 }, + { source: 'wissen', target: 'kunst', strength: 0.6 }, + { source: 'wissen', target: 'psychologie', strength: 0.7 }, + { source: 'wissenschaft', target: 'technologie', strength: 0.9 }, + { source: 'wissenschaft', target: 'philosophie', strength: 0.5 }, + { source: 'philosophie', target: 'psychologie', strength: 0.6 } + ] + }; + try { - const data = await loadMindmapData(); - if (!data || !data.nodes || !data.edges) { - throw new Error('Ungültiges Datenformat: Mindmap-Daten fehlen oder sind unvollständig'); + // Versuche, Daten von der API zu laden + const response = await fetch('/api/mindmap/root'); + let data; + + if (response.ok) { + data = await response.json(); + + // Prüfe, ob die API korrekte Daten zurückgegeben hat + if (!data.nodes || !data.edges) { + console.warn('API-Antwort hat ungültiges Format, verwende Fallback-Daten'); + data = fallbackData; + } + } else { + console.warn('API-Anfrage fehlgeschlagen, verwende Fallback-Daten'); + data = fallbackData; } - + + // Cytoscape-Elemente erstellen const elements = [ // Knoten ...data.nodes.map(node => ({ @@ -201,8 +64,7 @@ async function initializeMindmap() { hasChildren: node.has_children, expanded: false, color: node.color_code, - fontColor: '#ffffff', - fontSize: node.is_center ? 20 : 16 + isCenter: node.is_center || false } })), // Kanten @@ -214,1279 +76,134 @@ async function initializeMindmap() { } })) ]; - - // Bestehende Cytoscape-Instanz entfernen, falls vorhanden - if (window.cy && typeof window.cy.destroy === 'function') { + + // Cytoscape initialisieren + const cyContainer = document.getElementById('cy'); + + if (!cyContainer) { + throw new Error('Container #cy nicht gefunden'); + } + + // Bestehende Cytoscape-Instanz zerstören, wenn vorhanden + if (window.cy) { window.cy.destroy(); } - - const cyContainer = document.getElementById('cy'); - if (!cyContainer) { - throw new Error('Mindmap-Container #cy nicht gefunden!'); - } - + + // Neue Cytoscape-Instanz erstellen window.cy = cytoscape({ container: cyContainer, elements: elements, style: [ { selector: 'node', - style: mindmapStyles.node.base + style: { + 'background-color': 'data(color)', + 'label': 'data(label)', + 'color': '#ffffff', + 'text-outline-color': 'black', + 'text-outline-width': 1, + 'text-valign': 'center', + 'text-halign': 'center', + 'font-size': 18, + 'width': 50, + 'height': 50, + 'border-width': 2, + 'border-color': '#ffffff', + 'border-opacity': 0.6 + } }, { selector: 'node[isCenter]', - style: mindmapStyles.node.center - }, - { - selector: 'node:selected', - style: mindmapStyles.node.selected + style: { + 'background-color': '#f5f5f5', + 'color': '#222222', + 'font-size': 24, + 'width': 80, + 'height': 80 + } }, { selector: 'edge', - style: mindmapStyles.edge.base - } - ], - layout: mindmapStyles.layout.base - }); - - // 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 - }); - }); - - // Event-Listener für Knoten-Klicks - cy.on('tap', 'node', async function(evt) { - const node = evt.target; - console.log('Node clicked:', node.id(), 'hasChildren:', node.data('hasChildren'), 'expanded:', node.data('expanded')); - - if (node.data('hasChildren') && !node.data('expanded')) { - await loadSubthemes(node); - } - }); - - // Layout ausführen - cy.layout(mindmapStyles.layout.base).run(); - - // Starte neuronale Aktivitätssimulation - startNeuralActivitySimulation(cy); - - // Mindmap mit echten Daten befüllen (Styles, Farben etc.) - updateMindmap(); - - return true; - } catch (error) { - console.error('Fehler bei der Mindmap-Initialisierung:', error); - 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!'); - return; - } - console.log('Cytoscape ist verfügbar'); - - // Initialisiere die Mindmap - 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, - hasChildren: node.has_children, - 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 (window.neuralInterval) clearInterval(window.neuralInterval); - - const nodes = cy.nodes(); - let currentTime = Date.now(); - - function simulateNeuralActivity() { - currentTime = Date.now(); - - nodes.forEach(node => { - const data = node.data(); - const lastFired = data.lastFired || 0; - const timeSinceLastFire = currentTime - lastFired; - - if (timeSinceLastFire > data.refractionPeriod) { - if (Math.random() < data.neuronActivity * 0.1) { - fireNeuron(node, true, currentTime); - } - } - }); - } - - function fireNeuron(node, state, currentTime) { - const data = node.data(); - data.lastFired = currentTime; - - node.style({ - 'background-opacity': 1, - 'border-width': 3 - }); - - setTimeout(() => { - node.style({ - 'background-opacity': 0.9, - 'border-width': 2 - }); - }, 200); - - if (state) { - propagateSignal(node, currentTime); - } - } - - function propagateSignal(sourceNode, currentTime) { - const outgoingEdges = sourceNode.connectedEdges(); - - outgoingEdges.forEach(edge => { - const targetNode = edge.target(); - const edgeData = edge.data(); - const latency = edgeData.latency; - - edge.style({ - 'line-opacity': 0.8, - 'width': edgeData.strength * 3 - }); - - setTimeout(() => { - edge.style({ - 'line-opacity': edgeData.strength * 0.6, - 'width': edgeData.strength * 2 - }); - }, 200); - - setTimeout(() => { - const targetData = targetNode.data(); - 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); + style: { + 'width': function(ele) { + return ele.data('strength') * 5; + }, + 'line-color': 'rgba(255, 255, 255, 0.5)', + 'opacity': 0.7, + 'curve-style': 'bezier' } } - }, latency); + ], + layout: { + name: 'cose', + idealEdgeLength: 100, + nodeOverlap: 20, + refresh: 20, + fit: true, + padding: 30, + randomize: false, + componentSpacing: 100, + nodeRepulsion: 400000, + edgeElasticity: 100, + nestingFactor: 5, + gravity: 80, + numIter: 1000, + initialTemp: 200, + coolingFactor: 0.95, + minTemp: 1.0 + } }); + + // Knoten sperren, damit sie nicht verschoben werden können + window.cy.nodes().lock(); + + console.log('Mindmap erfolgreich initialisiert'); + return window.cy; + } catch (error) { + console.error('Fehler bei der Initialisierung der Mindmap:', error); + throw error; } - - window.neuralInterval = setInterval(simulateNeuralActivity, 100); -} - -// 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) + * Speichert Änderungen an der Mindmap + * @param {Object} cy - Die Cytoscape-Instanz */ -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; +function saveMindmapChanges(cy) { + console.log('Speichere Mindmap-Änderungen...'); if (!cy) { - console.error('Keine Cytoscape-Instanz gefunden'); + console.error('Keine Cytoscape-Instanz übergeben'); 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; + // Sammle alle Knoten und Kanten + const nodes = cy.nodes().map(node => ({ + id: node.id(), + name: node.data('label'), + category: node.data('category'), + description: node.data('description'), + has_children: node.data('hasChildren'), + is_center: node.data('isCenter'), + color_code: node.data('color'), + position: node.position() + })); - // Generiere eine eindeutige ID - const newId = 'new-node-' + Date.now(); + const edges = cy.edges().map(edge => ({ + source: edge.source().id(), + target: edge.target().id(), + strength: edge.data('strength') + })); - // 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 + // Daten zum Speichern vorbereiten const saveData = { nodes: nodes, - edges: edges, - parent_id: nodeId || null + edges: edges }; - console.log('Speichere Mindmap-Änderungen:', saveData); + console.log('Zu speichernde Daten:', saveData); - // Zeige eine Speicherbenachrichtigung an - showUINotification('Speichere Mindmap-Änderungen...', 'info'); - - // Sende die Daten an den Server + // API-Anfrage senden fetch('/api/mindmap/save', { method: 'POST', headers: { @@ -1494,265 +211,24 @@ function saveMindmapChanges(cy, nodeId) { }, 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(response => response.json()) .then(data => { - console.log('Speichern erfolgreich:', data); - showUINotification('Mindmap wurde erfolgreich gespeichert', 'success'); + if (data.success) { + // Erfolg + console.log('Mindmap erfolgreich gespeichert:', data); + alert('Mindmap wurde erfolgreich gespeichert!'); + } else { + // Fehler + console.error('Fehler beim Speichern der Mindmap:', data.error); + alert('Fehler beim Speichern: ' + data.error); + } }) .catch(error => { - console.error('Fehler beim Speichern der Mindmap:', error); - showUINotification({ - error: 'Fehler beim Speichern der Mindmap', - details: error.message - }, 'error'); + console.error('Netzwerkfehler beim Speichern der Mindmap:', error); + alert('Netzwerkfehler beim Speichern: ' + error.message); }); } -// 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); \ No newline at end of file +// Exportiere die Funktionen +window.initializeMindmap = initializeMindmap; +window.saveMindmapChanges = saveMindmapChanges; \ No newline at end of file diff --git a/templates/mindmap.html b/templates/mindmap.html index 84cbf50..f9a080e 100644 --- a/templates/mindmap.html +++ b/templates/mindmap.html @@ -90,7 +90,7 @@ /* Kontrollpanel */ .control-panel { position: absolute; - right: 2rem; + right: 9rem; top: 50%; transform: translateY(-50%); background: rgba(15, 23, 42, 0.9); @@ -123,6 +123,60 @@ margin-right: 0.75rem; } + /* Rechte Seitenleiste */ + .right-sidebar { + position: absolute; + right: 1.5rem; + top: 50%; + transform: translateY(-50%); + display: flex; + flex-direction: column; + gap: 1.5rem; + z-index: 100; + } + + .tool-buttons { + display: flex; + flex-direction: column; + gap: 0.75rem; + background: rgba(15, 23, 42, 0.9); + border-radius: 1rem; + padding: 1rem; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.1); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + } + + .sidebar-button { + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.75rem; + padding: 0.75rem; + background: rgba(255, 255, 255, 0.1); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 0.5rem; + color: white; + cursor: pointer; + transition: all 0.3s ease; + font-size: 0.9rem; + width: 100%; + } + + .sidebar-button:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateX(-5px); + } + + .sidebar-button i { + font-size: 1.1rem; + color: #8b5cf6; + } + + .sidebar-button:active { + transform: scale(0.98); + } + /* CRUD Panel */ .crud-panel { position: absolute; @@ -425,6 +479,24 @@ + + +