From dc962520130c016e5537092f474bfc176dc5798d Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Mon, 12 May 2025 20:48:26 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat:=20enhance=20mindmap=20functio?= =?UTF-8?q?nality=20with=20improved=20error=20handling=20and=20editing=20f?= =?UTF-8?q?eatures;=20update=20logging=20for=20better=20debugging=20insigh?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database/systades.db | Bin 143360 -> 143360 bytes logs/app.log | 38 ++ static/js/update_mindmap.js | 1265 ++++++++++++++++++++++++++++++++++- 3 files changed, 1276 insertions(+), 27 deletions(-) diff --git a/database/systades.db b/database/systades.db index 0061f997d33db0bbd9ea1f2fbb2f2f2473a4181d..4f5dd31e67287d1120dcd01efaf68238c28d99a7 100644 GIT binary patch delta 198 zcmZp8z|ru4V}dlJ%0wAwMwN{T_HsNX=2nKrdL|~O<`$-#SIb2P^04roX5gR5@6XT6 zcY3p+!fw8L2Nn(nQC?0*M~

${node.data('label')}

+
+ +
`; - const newContainer = document.createElement('div'); - newContainer.id = `cy-${node.id()}`; - newContainer.className = 'mindmap-view'; + // Erstelle den Container für das neue Cytoscape + const newCyContainer = document.createElement('div'); + newCyContainer.id = `cy-${node.id()}`; + newCyContainer.className = 'mindmap-view'; + // Füge die Elemente zur Seite hinzu newPage.appendChild(header); - newPage.appendChild(newContainer); + newPage.appendChild(newCyContainer); mindmapContainer.appendChild(newPage); + // Aktuelles Cy-Element ausblenden + if (cy && cy.container()) { + cy.container().style.display = 'none'; + } + + // Initialisiere das neue Cytoscape const newCy = cytoscape({ - container: newContainer, + container: newCyContainer, elements: [ - ...mindmapData.nodes.map(node => ({ + // Knoten + ...data.nodes.map(node => ({ data: { id: node.id, - label: node.name, + label: node.name || node.label, category: node.category, description: node.description, hasChildren: node.has_children, expanded: false, - color: node.color_code, + color: node.color_code || (node.category && mindmapConfig.categories[node.category] + ? mindmapConfig.categories[node.category].color + : '#60a5fa'), fontColor: '#ffffff', fontSize: 16 } })), - ...mindmapData.edges.map(edge => ({ + // Kanten + ...data.edges.map(edge => ({ data: { - source: edge.source_id, - target: edge.target_id, + source: edge.source || edge.source_id, + target: edge.target || edge.target_id, strength: edge.strength || 0.5 } })) ], - style: cy.style(), + 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 }); + // Füge neuronale Eigenschaften zu allen Knoten hinzu + newCy.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 + newCy.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 die neue Mindmap newCy.on('tap', 'node', async function(evt) { const clickedNode = evt.target; + console.log('Node clicked in subtheme:', clickedNode.id(), 'hasChildren:', clickedNode.data('hasChildren'), 'expanded:', clickedNode.data('expanded')); + if (clickedNode.data('hasChildren') && !clickedNode.data('expanded')) { await loadSubthemes(clickedNode); } }); + + // Starte neuronale Aktivitätssimulation für die neue Mindmap + startNeuralActivitySimulation(newCy); - // Alte Seite ausblenden und neue anzeigen - cy.container().style.display = 'none'; + // Speichere die Cytoscape-Instanz in einem globalen Array, damit wir sie später referenzieren können + if (!window.subthemeCyInstances) { + window.subthemeCyInstances = {}; + } + window.subthemeCyInstances[node.id()] = newCy; + + // Layout ausführen + newCy.layout(mindmapStyles.layout.base).run(); + + // Zeige die neue Seite an newPage.style.display = 'block'; - showFlash('Subthemen erfolgreich geladen', 'success'); - + showUINotification('Subthemen erfolgreich geladen', 'success'); + return true; } catch (error) { console.error('Fehler beim Laden der Subthemen:', error); - showFlash('Fehler beim Laden der Subthemen', 'error'); + showUINotification({ + error: 'Subthemen konnten nicht geladen werden', + details: error.message + }, 'error'); + return false; } } // Funktion zum Zurücknavigieren function goBack() { - const currentPage = document.querySelector('.mindmap-page:not([style*="display: none"])'); - if (currentPage) { - currentPage.style.display = 'none'; - cy.container().style.display = 'block'; + try { + // Finde die aktuell angezeigte Mindmap-Seite + const currentPage = document.querySelector('.mindmap-page:not([style*="display: none"])'); + if (!currentPage) { + console.warn('Keine Mindmap-Seite gefunden, zu der zurückgekehrt werden kann'); + return; + } + + // Finde die übergeordnete Node ID + const parentNodeId = currentPage.getAttribute('data-parent-node'); + if (!parentNodeId) { + console.warn('Keine übergeordnete Node-ID gefunden, zeige die Hauptmindmap an'); + + // Blende das aktuelle Cy-Element aus + if (window.cy && window.cy.container()) { + window.cy.container().style.display = 'block'; + } + + // Entferne die aktuelle Seite + currentPage.style.display = 'none'; + setTimeout(() => { + if (currentPage.parentNode) { + currentPage.parentNode.removeChild(currentPage); + } + }, 300); + + return; + } + + console.log('Navigiere zurück von Knoten:', parentNodeId); + + // Entferne die aktuelle Cytoscape-Instanz aus dem Array + if (window.subthemeCyInstances && window.subthemeCyInstances[parentNodeId]) { + try { + if (typeof window.subthemeCyInstances[parentNodeId].destroy === 'function') { + window.subthemeCyInstances[parentNodeId].destroy(); + } + delete window.subthemeCyInstances[parentNodeId]; + } catch (e) { + console.error('Fehler beim Zerstören der Cytoscape-Instanz:', e); + } + } + + // Prüfe, ob es eine übergeordnete Mindmap-Seite gibt + const parentPage = document.querySelector(`.mindmap-page[data-parent-node]:not([data-parent-node="${parentNodeId}"])`); + + if (parentPage) { + // Wenn eine übergeordnete Seite gefunden wurde, zeige sie an + parentPage.style.display = 'block'; + + // Entferne die aktuelle Seite + currentPage.style.display = 'none'; + setTimeout(() => { + if (currentPage.parentNode) { + currentPage.parentNode.removeChild(currentPage); + } + }, 300); + } else { + // Wenn keine übergeordnete Seite gefunden wurde, zeige die Hauptmindmap an + if (window.cy && window.cy.container()) { + window.cy.container().style.display = 'block'; + } + + // Entferne die aktuelle Seite + currentPage.style.display = 'none'; + setTimeout(() => { + if (currentPage.parentNode) { + currentPage.parentNode.removeChild(currentPage); + } + }, 300); + } + + showUINotification('Zurück zur übergeordneten Mindmap', 'info'); + } catch (error) { + console.error('Fehler bei der Rücknavigation:', error); + showUINotification('Fehler bei der Rücknavigation', 'error'); } } @@ -1057,4 +1248,1024 @@ style.textContent = ` document.head.appendChild(style); // Initialisiere die Mindmap beim Laden der Seite -document.addEventListener('DOMContentLoaded', initializeMindmap); \ No newline at end of file +document.addEventListener('DOMContentLoaded', initializeMindmap); + +// Funktion zum Aktivieren des Bearbeitungsmodus +function enableMindmapEditing(nodeId) { + try { + console.log('Aktiviere Bearbeitungsmodus für Mindmap:', nodeId); + + // Finde die relevante Cytoscape-Instanz + let targetCy; + if (nodeId) { + // Für Unterthemen + if (window.subthemeCyInstances && window.subthemeCyInstances[nodeId]) { + targetCy = window.subthemeCyInstances[nodeId]; + } else { + throw new Error(`Cytoscape-Instanz für Node ${nodeId} nicht gefunden`); + } + } else { + // Für die Hauptmindmap + targetCy = window.cy; + } + + if (!targetCy) { + throw new Error('Keine aktive Cytoscape-Instanz gefunden'); + } + + // Aktiviere Bearbeitungsmodus + toggleEditingMode(targetCy, true); + + // Zeige Bearbeitungssteuerungen an + showEditingControls(nodeId); + + showUINotification('Bearbeitungsmodus aktiviert', 'info'); + } catch (error) { + console.error('Fehler beim Aktivieren des Bearbeitungsmodus:', error); + showUINotification('Fehler beim Aktivieren des Bearbeitungsmodus', 'error'); + } +} + +// Funktion zum Deaktivieren des Bearbeitungsmodus +function disableMindmapEditing(nodeId) { + try { + console.log('Deaktiviere Bearbeitungsmodus für Mindmap:', nodeId); + + // Finde die relevante Cytoscape-Instanz + let targetCy; + if (nodeId) { + // Für Unterthemen + if (window.subthemeCyInstances && window.subthemeCyInstances[nodeId]) { + targetCy = window.subthemeCyInstances[nodeId]; + } + } else { + // Für die Hauptmindmap + targetCy = window.cy; + } + + if (!targetCy) { + throw new Error('Keine aktive Cytoscape-Instanz gefunden'); + } + + // Deaktiviere Bearbeitungsmodus + toggleEditingMode(targetCy, false); + + // Verstecke Bearbeitungssteuerungen + hideEditingControls(nodeId); + + showUINotification('Bearbeitungsmodus deaktiviert', 'info'); + } catch (error) { + console.error('Fehler beim Deaktivieren des Bearbeitungsmodus:', error); + showUINotification('Fehler beim Deaktivieren des Bearbeitungsmodus', 'error'); + } +} + +// Funktion zum Umschalten des Bearbeitungsmodus +function toggleEditingMode(cy, enabled) { + if (!cy) return; + + if (enabled) { + // Mache Knoten beweglich + cy.nodes().ungrabify(false); + + // Ändere den Cursor-Stil + cy.container().classList.add('editing-mode'); + + // Aktiviere Ziehen und Ablegen + cy.on('dragfree', 'node', function(event) { + const node = event.target; + console.log('Node verschoben:', node.id(), node.position()); + // Hier könnte man die neue Position in der Datenbank speichern + }); + + // Aktiviere Doppelklick zum Bearbeiten + cy.on('dblclick', 'node', function(event) { + const node = event.target; + editNodeProperties(node); + }); + + // Aktiviere Rechtsklick-Menü + cy.on('cxttap', 'node', function(event) { + const node = event.target; + showNodeContextMenu(node, event.renderedPosition); + }); + + // Aktiviere Rechtsklick auf leeren Bereich zum Hinzufügen neuer Knoten + cy.on('cxttap', function(event) { + if (event.target === cy) { + showAddNodeMenu(event.renderedPosition); + } + }); + } else { + // Deaktiviere Bearbeitungsfunktionen + cy.nodes().grabify(); + cy.container().classList.remove('editing-mode'); + cy.removeListener('dragfree'); + cy.removeListener('dblclick'); + cy.removeListener('cxttap'); + } +} + +// 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); \ No newline at end of file