diff --git a/database/systades.db b/database/systades.db
index 0061f99..4f5dd31 100644
Binary files a/database/systades.db and b/database/systades.db differ
diff --git a/logs/app.log b/logs/app.log
index 45c5f0c..ffc2be8 100644
--- a/logs/app.log
+++ b/logs/app.log
@@ -5874,3 +5874,41 @@ werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on
2025-05-12 20:27:23,378 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-12 20:27:23,378 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
2025-05-12 20:27:37,548 INFO: Anwendung gestartet [in /home/core/dev/website/app.py:77]
+2025-05-12 20:37:30,370 ERROR: Fehler 500: 405 Method Not Allowed: The method is not allowed for the requested URL.
+Endpoint: /api/thoughts, Method: GET, IP: 127.0.0.1
+User: 1 (admin)
+Traceback (most recent call last):
+ File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request
+ rv = self.dispatch_request()
+ ^^^^^^^^^^^^^^^^^^^^^^^
+ File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 1458, in dispatch_request
+ self.raise_routing_exception(req)
+ File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 1440, in raise_routing_exception
+ raise request.routing_exception # type: ignore
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
+ result = self.url_adapter.match(return_rule=True) # type: ignore
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 650, in match
+ raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None
+werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.
+ [in /home/core/dev/website/app.py:93]
+2025-05-12 20:37:33,682 ERROR: Fehler 500: 405 Method Not Allowed: The method is not allowed for the requested URL.
+Endpoint: /api/thoughts, Method: GET, IP: 127.0.0.1
+User: 1 (admin)
+Traceback (most recent call last):
+ File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 1484, in full_dispatch_request
+ rv = self.dispatch_request()
+ ^^^^^^^^^^^^^^^^^^^^^^^
+ File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 1458, in dispatch_request
+ self.raise_routing_exception(req)
+ File "/home/core/.local/lib/python3.11/site-packages/flask/app.py", line 1440, in raise_routing_exception
+ raise request.routing_exception # type: ignore
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ File "/home/core/.local/lib/python3.11/site-packages/flask/ctx.py", line 353, in match_request
+ result = self.url_adapter.match(return_rule=True) # type: ignore
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ File "/home/core/.local/lib/python3.11/site-packages/werkzeug/routing/map.py", line 650, in match
+ raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None
+werkzeug.exceptions.MethodNotAllowed: 405 Method Not Allowed: The method is not allowed for the requested URL.
+ [in /home/core/dev/website/app.py:93]
diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js
index 13d61ec..419cf71 100644
--- a/static/js/update_mindmap.js
+++ b/static/js/update_mindmap.js
@@ -806,16 +806,50 @@ function createFlashContainer() {
// Funktion zum Laden der Subthemen
async function loadSubthemes(node) {
try {
- const mindmapData = await loadMindmapData(node.id());
- if (!mindmapData) return;
+ // Prüfe zuerst, ob die Node gültig ist
+ if (!node || !node.id) {
+ console.error('Ungültige Node beim Laden der Subthemen');
+ showUINotification('Fehler: Ungültiger Knoten für Subthemen', 'error');
+ return;
+ }
- showFlash('Lade Subthemen...', 'info');
+ // Zeige Ladebenachrichtigung
+ showUINotification('Lade Subthemen...', 'info');
+ console.log('Lade Subthemen für Node:', node.id());
- const mindmapContainer = document.querySelector('.mindmap-container');
+ // Lade die Daten für die Unterthemen
+ const data = await loadMindmapData(node.id());
+ if (!data || !data.nodes || !data.edges) {
+ throw new Error('Ungültiges Datenformat: Subthemen-Daten fehlen oder sind unvollständig');
+ }
+
+ // Markiere den aktuellen Knoten als erweitert
+ node.data('expanded', true);
+
+ // Finde den Mindmap-Container
+ let mindmapContainer = document.querySelector('.mindmap-container');
+ if (!mindmapContainer) {
+ // Falls der Container nicht existiert, versuche den cy-Container zu finden und erstelle einen Wrapper
+ const cyContainer = document.getElementById('cy');
+ if (!cyContainer) {
+ throw new Error('Mindmap-Container nicht gefunden');
+ }
+
+ // Erstelle einen Container für die Mindmap-Seiten
+ const parentElement = cyContainer.parentElement;
+ mindmapContainer = document.createElement('div');
+ mindmapContainer.className = 'mindmap-container';
+ parentElement.insertBefore(mindmapContainer, cyContainer);
+ parentElement.removeChild(cyContainer);
+ mindmapContainer.appendChild(cyContainer);
+ }
+
+ // Erstelle eine neue Seite für die Unterthemen
const newPage = document.createElement('div');
newPage.className = 'mindmap-page';
- newPage.style.display = 'none';
+ newPage.setAttribute('data-parent-node', node.id());
+ // Erstelle den Header der Seite
const header = document.createElement('div');
header.className = 'mindmap-header';
header.innerHTML = `
@@ -825,70 +859,227 @@ async function loadSubthemes(node) {
${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 = `
+
+
+
+ `;
+
+ 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 += `
+
+ `;
+ }
+
+ // Menü-Einträge
+ menu.innerHTML = `
+
+ ${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