diff --git a/logs/app.log b/logs/app.log
index 0ae7e59..7791ffc 100644
--- a/logs/app.log
+++ b/logs/app.log
@@ -425,3 +425,6 @@ werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on
2025-05-14 12:13:05,415 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77]
2025-05-14 12:13:07,885 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77]
2025-05-14 12:13:07,885 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77]
+2025-05-14 12:13:20,274 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77]
+2025-05-14 12:13:22,949 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77]
+2025-05-14 12:13:22,949 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77]
diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js
index 2d266f2..2e97f87 100644
--- a/static/js/update_mindmap.js
+++ b/static/js/update_mindmap.js
@@ -968,422 +968,237 @@ function handleEditingAction(action, nodeId) {
}
// 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 }
+function addNewNode(cy, parentNode = null) {
+ // Dialog erstellen
+ const dialog = document.createElement('div');
+ dialog.className = 'node-dialog';
+ dialog.style.position = 'absolute';
+ dialog.style.top = '50%';
+ dialog.style.left = '50%';
+ dialog.style.transform = 'translate(-50%, -50%)';
+ dialog.style.background = 'rgba(15, 23, 42, 0.95)';
+ dialog.style.padding = '1.5rem';
+ dialog.style.borderRadius = '0.75rem';
+ dialog.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)';
+ dialog.style.zIndex = '2000';
+ dialog.style.width = '400px';
+ dialog.style.maxWidth = '90vw';
+ dialog.style.backdropFilter = 'blur(8px)';
+ dialog.style.border = '1px solid rgba(255, 255, 255, 0.1)';
+
+ dialog.innerHTML = `
+
Neuen Knoten erstellen
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `;
+
+ document.body.appendChild(dialog);
+
+ // Event-Listener
+ document.getElementById('cancel-node').addEventListener('click', () => {
+ document.body.removeChild(dialog);
+ });
+
+ document.getElementById('create-node').addEventListener('click', () => {
+ const name = document.getElementById('node-name').value || 'Neuer Knoten';
+ const description = document.getElementById('node-description').value || '';
+ const category = document.getElementById('node-category').value;
+ const color = document.getElementById('node-color').value;
+
+ // Temporäre ID erstellen (wird später durch die echte ID ersetzt)
+ const tempId = 'new-' + Date.now();
+
+ // Knoten zur Visualisierung hinzufügen
+ const newNode = cy.add({
+ group: 'nodes',
+ data: {
+ id: tempId,
+ label: name,
+ description: description,
+ category: category,
+ color: color,
+ icon: 'fa-solid fa-circle'
+ },
+ position: {
+ x: cy.width() / 2 + (Math.random() * 100 - 50),
+ y: cy.height() / 2 + (Math.random() * 100 - 50)
+ }
+ });
+
+ // Wenn ein Elternknoten angegeben ist, eine Verbindung erstellen
+ if (parentNode) {
+ cy.add({
+ group: 'edges',
+ data: {
+ source: parentNode.id(),
+ target: tempId,
+ strength: 0.5
+ }
+ });
+ }
+
+ // Layout neu berechnen
+ cy.layout(mindmapStyles.layout.base).run();
+
+ document.body.removeChild(dialog);
+ showUINotification('Neuer Knoten erstellt!', 'success');
+ });
+
+ // Kategorie-Auswahl mit Farben verknüpfen
+ document.getElementById('node-category').addEventListener('change', (e) => {
+ const category = e.target.value;
+ let color = '#9F7AEA'; // Standardfarbe
+
+ switch(category) {
+ case 'Philosophie':
+ color = '#9F7AEA'; // Lila
+ break;
+ case 'Wissenschaft':
+ color = '#60A5FA'; // Blau
+ break;
+ case 'Technologie':
+ color = '#10B981'; // Grün
+ break;
+ case 'Künste':
+ color = '#F59E0B'; // Orange
+ break;
+ case 'Psychologie':
+ color = '#EF4444'; // Rot
+ break;
+ }
+
+ document.getElementById('node-color').value = color;
});
-
- // 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
+// Funktion zum Bearbeiten eines Knotens
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 erstellen
+ const dialog = document.createElement('div');
+ dialog.className = 'node-dialog';
+ dialog.style.position = 'absolute';
+ dialog.style.top = '50%';
+ dialog.style.left = '50%';
+ dialog.style.transform = 'translate(-50%, -50%)';
+ dialog.style.background = 'rgba(15, 23, 42, 0.95)';
+ dialog.style.padding = '1.5rem';
+ dialog.style.borderRadius = '0.75rem';
+ dialog.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)';
+ dialog.style.zIndex = '2000';
+ dialog.style.width = '400px';
+ dialog.style.maxWidth = '90vw';
+ dialog.style.backdropFilter = 'blur(8px)';
+ dialog.style.border = '1px solid rgba(255, 255, 255, 0.1)';
+
dialog.innerHTML = `
- Knoten bearbeiten
-
+ Knoten bearbeiten
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
`;
-
+
document.body.appendChild(dialog);
-
- // Event-Listener für den Abbrechen-Button
- dialog.querySelector('button.cancel').addEventListener('click', function() {
+
+ // Event-Listener
+ document.getElementById('cancel-edit').addEventListener('click', () => {
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;
+
+ document.getElementById('save-edit').addEventListener('click', () => {
+ const name = document.getElementById('node-name').value || 'Unbenannter Knoten';
+ const description = document.getElementById('node-description').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
+ const color = document.getElementById('node-color').value;
+
+ // Knoten aktualisieren
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',
+ label: name,
+ description: description,
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
- }
+ color: color
});
-
- 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);
+ document.body.removeChild(dialog);
+ showUINotification('Knoten wurde aktualisiert!', 'success');
});
-
- // 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);
+
+ // Kategorie-Auswahl mit Farben verknüpfen
+ document.getElementById('node-category').addEventListener('change', (e) => {
+ const category = e.target.value;
+ let color = '#9F7AEA'; // Standardfarbe
+
+ switch(category) {
+ case 'Philosophie':
+ color = '#9F7AEA'; // Lila
+ break;
+ case 'Wissenschaft':
+ color = '#60A5FA'; // Blau
+ break;
+ case 'Technologie':
+ color = '#10B981'; // Grün
+ break;
+ case 'Künste':
+ color = '#F59E0B'; // Orange
+ break;
+ case 'Psychologie':
+ color = '#EF4444'; // Rot
+ break;
}
+
+ document.getElementById('node-color').value = color;
});
}
@@ -1391,130 +1206,275 @@ function enableEdgeCreationMode(cy) {
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
+ if (confirm(`Möchten Sie den Knoten "${node.data('label')}" wirklich löschen?`)) {
+ // Alle verbundenen Kanten löschen
+ node.connectedEdges().remove();
+ // Knoten löschen
node.remove();
- showUINotification('Knoten wurde gelöscht', 'success');
+ showUINotification('Knoten wurde gelöscht!', 'success');
}
}
-// Funktion zum Löschen ausgewählter Elemente
-function deleteSelectedElements(cy) {
- if (!cy) return;
+// Kontextmenü für Knoten anzeigen
+function showNodeContextMenu(node, position) {
+ // Entferne vorhandene Kontextmenüs
+ removeContextMenus();
- const selectedElements = cy.elements(':selected');
- if (selectedElements.length === 0) {
- showUINotification('Keine Elemente ausgewählt', 'warning');
- return;
- }
+ const contextMenu = document.createElement('div');
+ contextMenu.className = 'context-menu';
+ contextMenu.style.left = `${position.x}px`;
+ contextMenu.style.top = `${position.y}px`;
- // Zähle die ausgewählten Knoten und Kanten
- const selectedNodes = selectedElements.filter('node');
- const selectedEdges = selectedElements.filter('edge');
+ contextMenu.innerHTML = `
+
+
+
+ `;
- 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?`;
- }
+ document.body.appendChild(contextMenu);
- // 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`;
+ // Event-Listener für Menüpunkte
+ contextMenu.querySelectorAll('.context-menu-item').forEach(item => {
+ item.addEventListener('click', function() {
+ const action = this.getAttribute('data-action');
+
+ switch (action) {
+ case 'edit':
+ editNodeProperties(node);
+ break;
+ case 'add-child':
+ addNewNode(window.cy, node);
+ break;
+ case 'delete':
+ deleteNode(node);
+ break;
+ }
+
+ removeContextMenus();
+ });
+ });
+
+ // Event-Listener zum Schließen des Menüs
+ document.addEventListener('click', function closeMenu(e) {
+ if (!contextMenu.contains(e.target)) {
+ removeContextMenus();
+ document.removeEventListener('click', closeMenu);
}
-
- showUINotification(successMessage, 'success');
- }
+ });
}
-// Funktion zum Speichern der Änderungen an der Mindmap
-function saveMindmapChanges(cy, nodeId) {
- if (!cy) return;
+// Funktion zum Hinzufügen eines Knotens an einer bestimmten Position (Rechtsklick)
+function showAddNodeMenu(position) {
+ // Entferne vorhandene Kontextmenüs
+ removeContextMenus();
- // Sammle die Daten aller Knoten und Kanten
- const nodes = [];
- const edges = [];
+ const contextMenu = document.createElement('div');
+ contextMenu.className = 'context-menu';
+ contextMenu.style.left = `${position.x}px`;
+ contextMenu.style.top = `${position.y}px`;
- 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
+ contextMenu.innerHTML = `
+
+ `;
+
+ document.body.appendChild(contextMenu);
+
+ // Event-Listener für Menüpunkte
+ contextMenu.querySelectorAll('.context-menu-item').forEach(item => {
+ item.addEventListener('click', function() {
+ const action = this.getAttribute('data-action');
+
+ if (action === 'add-node') {
+ // Position im Canvas ermitteln
+ const containerPos = window.cy.container().getBoundingClientRect();
+ const cyPos = {
+ x: position.x - containerPos.left,
+ y: position.y - containerPos.top
+ };
+
+ // Temporäre ID erstellen
+ const tempId = 'new-' + Date.now();
+
+ // Knoten hinzufügen (vereinfachte Version, zeigt direkt den Dialog an)
+ addNewNode(window.cy);
+ }
+
+ removeContextMenus();
});
});
- cy.edges().forEach(edge => {
- const data = edge.data();
-
- edges.push({
- source: data.source,
- target: data.target,
- strength: data.strength
- });
+ // Event-Listener zum Schließen des Menüs
+ document.addEventListener('click', function closeMenu(e) {
+ if (!contextMenu.contains(e.target)) {
+ removeContextMenus();
+ document.removeEventListener('click', closeMenu);
+ }
});
+}
+
+// Hilfsfunktion zum Entfernen aller Kontextmenüs
+function removeContextMenus() {
+ document.querySelectorAll('.context-menu').forEach(menu => {
+ menu.remove();
+ });
+}
+
+// Funktion zum Aktivieren des Kanten-Erstellungsmodus
+function enableEdgeCreationMode(cy) {
+ // Status anzeigen
+ showUINotification('Bitte wählen Sie den Startknoten für die Verbindung', 'info');
- // Erstelle die Daten für den API-Aufruf
- const saveData = {
- nodes: nodes,
- edges: edges,
- parent_id: nodeId || null
+ // Cursor-Stil ändern
+ document.body.style.cursor = 'crosshair';
+ cy.container().classList.add('edge-creation-mode');
+
+ // Event-Handler für die Knotenauswahl
+ const nodeSelectHandler = function(event) {
+ // Ersten Knoten auswählen
+ const sourceNode = event.target;
+
+ // Status aktualisieren
+ showUINotification('Wählen Sie jetzt den Zielknoten für die Verbindung', 'info');
+
+ // Zweiten Knoten abwarten
+ const secondNodeHandler = function(event) {
+ const targetNode = event.target;
+
+ // Prüfen, ob der Zielknoten ein Knoten ist und nicht derselbe wie der Startknoten
+ if (targetNode.isNode() && targetNode.id() !== sourceNode.id()) {
+ // Kante erstellen
+ cy.add({
+ group: 'edges',
+ data: {
+ source: sourceNode.id(),
+ target: targetNode.id(),
+ strength: 0.5
+ }
+ });
+
+ // Erfolgsmeldung
+ showUINotification('Verbindung erstellt!', 'success');
+
+ // Event-Handler entfernen
+ cy.off('tap', 'node', secondNodeHandler);
+
+ // Modus beenden
+ finishEdgeCreation();
+ }
+ };
+
+ // Event-Handler für den zweiten Knoten
+ cy.on('tap', 'node', secondNodeHandler);
+
+ // Original-Handler entfernen
+ cy.off('tap', 'node', nodeSelectHandler);
+
+ // ESC-Taste zum Abbrechen
+ document.addEventListener('keydown', function escKeyHandler(e) {
+ if (e.key === 'Escape') {
+ cy.off('tap', 'node', secondNodeHandler);
+ finishEdgeCreation();
+ document.removeEventListener('keydown', escKeyHandler);
+ showUINotification('Verbindungserstellung abgebrochen', 'info');
+ }
+ });
};
- console.log('Speichere Mindmap-Änderungen:', saveData);
+ // Event-Handler für den ersten Knoten
+ cy.on('tap', 'node', nodeSelectHandler);
- // Zeige eine Speicherbenachrichtigung an
- showUINotification('Speichere Mindmap-Änderungen...', 'info');
+ // Funktion zum Beenden des Kantenerstellungsmodus
+ function finishEdgeCreation() {
+ document.body.style.cursor = '';
+ cy.container().classList.remove('edge-creation-mode');
+ }
- // 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}`);
- });
+ // Abbrechen durch Klick außerhalb
+ cy.on('tap', function(event) {
+ if (event.target === cy) {
+ cy.off('tap', 'node', nodeSelectHandler);
+ finishEdgeCreation();
+ showUINotification('Verbindungserstellung abgebrochen', 'info');
}
- 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');
});
}
+// Funktion zum Speichern von Mindmap-Änderungen
+async function saveMindmapChanges(cy) {
+ try {
+ showUINotification('Speichere Änderungen...', 'info');
+
+ // Alle Knoten und Kanten sammeln
+ const nodes = cy.nodes().map(node => {
+ const data = node.data();
+ return {
+ id: data.id,
+ name: data.label,
+ description: data.description || '',
+ color_code: data.color || data.color_code,
+ category: data.category || null,
+ icon: data.icon || 'fa-solid fa-circle',
+ position_x: node.position().x,
+ position_y: node.position().y
+ };
+ });
+
+ const edges = cy.edges().map(edge => {
+ return {
+ source: edge.source().id(),
+ target: edge.target().id(),
+ strength: edge.data('strength') || 0.5
+ };
+ });
+
+ // Daten an API senden
+ const response = await fetch('/api/mindmap/save', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ nodes: nodes,
+ edges: edges
+ })
+ });
+
+ const result = await response.json();
+
+ if (!response.ok || !result.success) {
+ const errorMessage = result.error || 'Mindmap konnte nicht gespeichert werden';
+ showUINotification(errorMessage, 'error');
+ throw new Error(errorMessage);
+ }
+
+ // Wenn temporäre IDs aktualisiert wurden, Mapping anwenden
+ if (result.node_mapping) {
+ // Die echten IDs hinzufügen
+ for (const [tempId, realId] of Object.entries(result.node_mapping)) {
+ const node = cy.getElementById(tempId);
+ if (node.length > 0) {
+ node.data('id', realId);
+ }
+ }
+ }
+
+ showUINotification('Mindmap wurde erfolgreich gespeichert!', 'success');
+ return result;
+ } catch (error) {
+ console.error('Fehler beim Speichern der Mindmap:', error);
+ showUINotification('Fehler beim Speichern: ' + error.message, 'error');
+ throw error;
+ }
+}
+
// Füge CSS-Stile für den Bearbeitungsmodus hinzu
const editingStyles = document.createElement('style');
editingStyles.textContent = `
diff --git a/templates/mindmap.html b/templates/mindmap.html
index ceef8e1..84cbf50 100644
--- a/templates/mindmap.html
+++ b/templates/mindmap.html
@@ -31,6 +31,9 @@
backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
z-index: 10;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
}
.mindmap-title {
@@ -43,6 +46,47 @@
-webkit-text-fill-color: transparent;
}
+ /* Aktionsmenü im Header */
+ .mindmap-actions {
+ display: flex;
+ gap: 0.75rem;
+ }
+
+ .action-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ padding: 0.5rem 1rem;
+ background: rgba(255, 255, 255, 0.1);
+ color: white;
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 0.5rem;
+ font-size: 0.9rem;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ }
+
+ .action-button:hover {
+ background: rgba(255, 255, 255, 0.2);
+ }
+
+ .action-button.primary {
+ background: rgba(139, 92, 246, 0.3);
+ }
+
+ .action-button.primary:hover {
+ background: rgba(139, 92, 246, 0.5);
+ }
+
+ .action-button.danger {
+ background: rgba(220, 38, 38, 0.3);
+ }
+
+ .action-button.danger:hover {
+ background: rgba(220, 38, 38, 0.5);
+ }
+
/* Kontrollpanel */
.control-panel {
position: absolute;
@@ -79,6 +123,85 @@
margin-right: 0.75rem;
}
+ /* CRUD Panel */
+ .crud-panel {
+ position: absolute;
+ bottom: 2rem;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(15, 23, 42, 0.9);
+ border-radius: 1rem;
+ padding: 1rem;
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
+ z-index: 10;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ display: flex;
+ gap: 0.75rem;
+ backdrop-filter: blur(8px);
+ }
+
+ .crud-button {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: 4rem;
+ height: 4rem;
+ background: rgba(255, 255, 255, 0.1);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+ border-radius: 0.75rem;
+ color: white;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ }
+
+ .crud-button:hover {
+ background: rgba(255, 255, 255, 0.2);
+ transform: translateY(-5px);
+ }
+
+ .crud-button i {
+ font-size: 1.25rem;
+ margin-bottom: 0.25rem;
+ }
+
+ .crud-button span {
+ font-size: 0.7rem;
+ text-align: center;
+ }
+
+ .crud-button.create {
+ background: rgba(16, 185, 129, 0.3);
+ }
+
+ .crud-button.create:hover {
+ background: rgba(16, 185, 129, 0.5);
+ }
+
+ .crud-button.edit {
+ background: rgba(245, 158, 11, 0.3);
+ }
+
+ .crud-button.edit:hover {
+ background: rgba(245, 158, 11, 0.5);
+ }
+
+ .crud-button.delete {
+ background: rgba(220, 38, 38, 0.3);
+ }
+
+ .crud-button.delete:hover {
+ background: rgba(220, 38, 38, 0.5);
+ }
+
+ .crud-button.save {
+ background: rgba(59, 130, 246, 0.3);
+ }
+
+ .crud-button.save:hover {
+ background: rgba(59, 130, 246, 0.5);
+ }
+
/* Info-Panel */
.info-panel {
position: absolute;
@@ -194,6 +317,53 @@
text-align: center;
max-width: 80%;
}
+
+ /* Bearbeitungsmodus-Hinweis */
+ .edit-mode-indicator {
+ position: fixed;
+ bottom: 1rem;
+ left: 1rem;
+ background: rgba(245, 158, 11, 0.8);
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 0.5rem;
+ font-size: 0.9rem;
+ z-index: 1000;
+ display: none;
+ }
+
+ .edit-mode-indicator.active {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ }
+
+ /* 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);
+ }
{% endblock %}
@@ -202,6 +372,22 @@
@@ -209,6 +395,12 @@
Lade Mindmap...
+
+
+
+
+ Bearbeitungsmodus
+
@@ -233,6 +425,30 @@
+
+
+
+
+
+
+
+
+
Knotendetails
@@ -281,6 +497,23 @@ document.addEventListener('DOMContentLoaded', function() {
const cyContainer = document.getElementById('cy');
const loader = document.getElementById('loader');
const statusMessage = document.getElementById('statusMessage');
+ const crudPanel = document.getElementById('crudPanel');
+ const editModeIndicator = document.getElementById('editModeIndicator');
+
+ // CRUD Buttons
+ const createNodeBtn = document.getElementById('createNode');
+ const createEdgeBtn = document.getElementById('createEdge');
+ const editNodeBtn = document.getElementById('editNode');
+ const deleteElementBtn = document.getElementById('deleteElement');
+ const saveMindmapBtn = document.getElementById('saveMindmap');
+
+ // Header Action Buttons
+ const toggleEditModeBtn = document.getElementById('toggleEditMode');
+ const saveChangesBtn = document.getElementById('saveChanges');
+ const cancelEditBtn = document.getElementById('cancelEdit');
+
+ let isEditMode = false;
+ let selectedElement = null;
if (cyContainer) {
console.log('Container gefunden:', cyContainer);
@@ -299,6 +532,56 @@ document.addEventListener('DOMContentLoaded', function() {
// Erfolg: Loader und Statusmeldung ausblenden
loader.style.display = 'none';
statusMessage.style.display = 'none';
+
+ // Event-Listener für Knotenauswahl
+ window.cy.on('select', 'node', function(event) {
+ selectedElement = event.target;
+ editNodeBtn.disabled = false;
+ deleteElementBtn.disabled = false;
+
+ // Knotendetails im Info-Panel anzeigen
+ showNodeInfo(selectedElement);
+ });
+
+ window.cy.on('select', 'edge', function(event) {
+ selectedElement = event.target;
+ editNodeBtn.disabled = true;
+ deleteElementBtn.disabled = false;
+ });
+
+ window.cy.on('unselect', function() {
+ selectedElement = null;
+ editNodeBtn.disabled = true;
+ deleteElementBtn.disabled = true;
+
+ // Info-Panel ausblenden
+ hideNodeInfo();
+ });
+
+ // Rechtsklick-Menü
+ window.cy.on('cxttap', 'node', function(event) {
+ // Kontextmenü für Knoten anzeigen
+ if (isEditMode) {
+ const node = event.target;
+ const position = event.renderedPosition;
+ showNodeContextMenu(node, {
+ x: event.originalEvent.clientX,
+ y: event.originalEvent.clientY
+ });
+ event.preventDefault();
+ }
+ });
+
+ window.cy.on('cxttap', function(event) {
+ // Kontextmenü zum Hinzufügen eines Knotens
+ if (isEditMode && event.target === window.cy) {
+ showAddNodeMenu({
+ x: event.originalEvent.clientX,
+ y: event.originalEvent.clientY
+ });
+ event.preventDefault();
+ }
+ });
}).catch(error => {
// Fehler: Fehlermeldung anzeigen
console.error('Mindmap-Initialisierung fehlgeschlagen', error);
@@ -318,6 +601,96 @@ document.addEventListener('DOMContentLoaded', function() {
console.error('Container #cy nicht gefunden');
}
+ // Bearbeitungsmodus umschalten
+ toggleEditModeBtn.addEventListener('click', function() {
+ isEditMode = !isEditMode;
+
+ if (isEditMode) {
+ // Bearbeitungsmodus aktivieren
+ crudPanel.style.display = 'flex';
+ editModeIndicator.classList.add('active');
+ toggleEditModeBtn.style.display = 'none';
+ saveChangesBtn.style.display = 'inline-flex';
+ cancelEditBtn.style.display = 'inline-flex';
+ window.cy.container().classList.add('editing-mode');
+
+ // Aktiviere Knotenbewegung (dragging)
+ window.cy.nodes().unlock();
+ } else {
+ // Bearbeitungsmodus deaktivieren
+ crudPanel.style.display = 'none';
+ editModeIndicator.classList.remove('active');
+ toggleEditModeBtn.style.display = 'inline-flex';
+ saveChangesBtn.style.display = 'none';
+ cancelEditBtn.style.display = 'none';
+ window.cy.container().classList.remove('editing-mode');
+
+ // Deaktiviere Knotenbewegung
+ window.cy.nodes().lock();
+ }
+ });
+
+ // Änderungen speichern
+ saveChangesBtn.addEventListener('click', function() {
+ saveMindmapChanges(window.cy);
+ });
+
+ // Bearbeitungsmodus abbrechen
+ cancelEditBtn.addEventListener('click', function() {
+ if (confirm('Möchten Sie den Bearbeitungsmodus wirklich verlassen? Nicht gespeicherte Änderungen gehen verloren.')) {
+ isEditMode = false;
+ crudPanel.style.display = 'none';
+ editModeIndicator.classList.remove('active');
+ toggleEditModeBtn.style.display = 'inline-flex';
+ saveChangesBtn.style.display = 'none';
+ cancelEditBtn.style.display = 'none';
+ window.cy.container().classList.remove('editing-mode');
+
+ // Neuinitialisierung der Mindmap
+ initializeMindmap().then(() => {
+ loader.style.display = 'none';
+ statusMessage.style.display = 'none';
+ });
+ }
+ });
+
+ // CRUD-Funktionen
+ createNodeBtn.addEventListener('click', function() {
+ if (isEditMode) {
+ addNewNode(window.cy);
+ }
+ });
+
+ createEdgeBtn.addEventListener('click', function() {
+ if (isEditMode) {
+ enableEdgeCreationMode(window.cy);
+ }
+ });
+
+ editNodeBtn.addEventListener('click', function() {
+ if (isEditMode && selectedElement && selectedElement.isNode()) {
+ editNodeProperties(selectedElement);
+ }
+ });
+
+ deleteElementBtn.addEventListener('click', function() {
+ if (isEditMode && selectedElement) {
+ if (selectedElement.isNode()) {
+ deleteNode(selectedElement);
+ } else if (selectedElement.isEdge()) {
+ if (confirm('Möchten Sie diese Verbindung wirklich löschen?')) {
+ selectedElement.remove();
+ }
+ }
+ }
+ });
+
+ saveMindmapBtn.addEventListener('click', function() {
+ if (isEditMode) {
+ saveMindmapChanges(window.cy);
+ }
+ });
+
// Zoom-Funktionen
document.getElementById('zoomIn').addEventListener('click', function() {
if (window.cy) window.cy.zoom(window.cy.zoom() * 1.2);
@@ -335,6 +708,36 @@ document.addEventListener('DOMContentLoaded', function() {
const legend = document.getElementById('categoryLegend');
legend.style.display = legend.style.display === 'none' ? 'flex' : 'none';
});
+
+ // Funktionen für Knoteninfo-Panel
+ function showNodeInfo(node) {
+ if (!node || !node.isNode()) return;
+
+ const infoPanel = document.getElementById('infoPanel');
+ const infoTitle = infoPanel.querySelector('.info-title');
+ const infoContent = infoPanel.querySelector('.info-content');
+
+ const data = node.data();
+
+ infoTitle.textContent = data.label || 'Knotendetails';
+
+ let contentHTML = `
+
Kategorie: ${data.category || 'Nicht kategorisiert'}
+
Beschreibung: ${data.description || 'Keine Beschreibung verfügbar'}
+ `;
+
+ if (data.hasChildren) {
+ contentHTML += `
Hat Unterknoten
`;
+ }
+
+ infoContent.innerHTML = contentHTML;
+ infoPanel.classList.add('visible');
+ }
+
+ function hideNodeInfo() {
+ const infoPanel = document.getElementById('infoPanel');
+ infoPanel.classList.remove('visible');
+ }
});
{% endblock %}
\ No newline at end of file