From b867af9c8b5f38f3ec402272ab266ff07fad2bfa Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Wed, 14 May 2025 12:47:02 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20=C3=84nderungen=20commited?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- logs/app.log | 12 + static/css/mindmap.css | 206 --------- static/js/update_mindmap.js | 874 +++++++++++++++++++----------------- 3 files changed, 469 insertions(+), 623 deletions(-) diff --git a/logs/app.log b/logs/app.log index 7791ffc..e550e79 100644 --- a/logs/app.log +++ b/logs/app.log @@ -428,3 +428,15 @@ werkzeug.exceptions.NotFound: 404 Not Found: The requested URL was not found on 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] +2025-05-14 12:45:09,092 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:11,512 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:11,512 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:15,327 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:17,576 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:17,576 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:22,725 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:25,195 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:25,195 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:29,299 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:31,561 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] +2025-05-14 12:45:31,561 INFO: Anwendung gestartet [in C:\Users\TTOMCZA.EMEA\Dev\website\app.py:77] diff --git a/static/css/mindmap.css b/static/css/mindmap.css index c1b7a4f..9fa9dcc 100644 --- a/static/css/mindmap.css +++ b/static/css/mindmap.css @@ -250,210 +250,4 @@ .dark .mindmap-tooltip { background: rgba(30, 41, 59, 0.9); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); -} - -/* Mindmap Styles - Stylesheets für die interaktive Mindmap-Funktionalität - Umfasst Bearbeitungsmodus, CRUD-Funktionen und UI-Elemente -*/ - -/* Bearbeitungsmodus-Indikator */ -#cy.editing-mode { - background: rgba(245, 158, 11, 0.1) !important; -} - -/* Edge-Erstellungsmodus */ -#cy.edge-creation-mode { - cursor: crosshair !important; -} - -/* Aktivierter Knoten */ -#cy .edge-source { - border-color: #f59e0b !important; - border-width: 4px !important; - border-style: dashed !important; -} - -/* Node-Dialog Overlay */ -.node-dialog-overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - background: rgba(0, 0, 0, 0.5); - backdrop-filter: blur(3px); - z-index: 1900; -} - -/* Kontext-Menü */ -.context-menu { - position: absolute; - z-index: 2000; - background: rgba(15, 23, 42, 0.95); - border-radius: 0.5rem; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); - min-width: 180px; - border: 1px solid rgba(255, 255, 255, 0.1); - backdrop-filter: blur(10px); - overflow: hidden; -} - -.context-menu-header { - padding: 0.5rem 1rem; - color: rgba(255, 255, 255, 0.7); - font-size: 0.8rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); -} - -.context-menu-item { - padding: 0.5rem 1rem; - display: flex; - align-items: center; - gap: 0.5rem; - color: rgba(255, 255, 255, 0.9); - cursor: pointer; - transition: all 0.2s ease; -} - -.context-menu-item:hover { - background: rgba(255, 255, 255, 0.1); -} - -/* Benachrichtigungen */ -#notification-container { - position: fixed; - top: 1rem; - right: 1rem; - z-index: 2000; - max-width: 400px; - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.notification { - padding: 1rem; - border-radius: 0.5rem; - background: rgba(15, 23, 42, 0.95); - color: white; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); - display: flex; - align-items: center; - gap: 0.75rem; - transform: translateX(120%); - transition: transform 0.3s ease-out; - backdrop-filter: blur(10px); - border: 1px solid rgba(255, 255, 255, 0.1); -} - -.notification i { - font-size: 1.25rem; -} - -.notification-success { - background: rgba(16, 185, 129, 0.9); -} - -.notification-error { - background: rgba(239, 68, 68, 0.9); -} - -.notification-warning { - background: rgba(245, 158, 11, 0.9); -} - -.notification-info { - background: rgba(59, 130, 246, 0.9); -} - -/* CRUD-Aktionen Styles */ -.crud-panel { - transition: transform 0.3s ease, opacity 0.3s ease; -} - -.crud-panel.hidden { - opacity: 0; - transform: translateY(50px); - pointer-events: none; -} - -/* Tooltip für CRUD-Buttons */ -.crud-button::after { - content: attr(data-tooltip); - position: absolute; - bottom: 100%; - left: 50%; - transform: translateX(-50%); - background: rgba(15, 23, 42, 0.95); - padding: 0.3rem 0.6rem; - border-radius: 0.4rem; - font-size: 0.7rem; - white-space: nowrap; - opacity: 0; - transition: opacity 0.2s ease; - pointer-events: none; - margin-bottom: 5px; -} - -.crud-button:hover::after { - opacity: 1; -} - -/* Visueller Indikator für den Bearbeitungsmodus */ -.edit-mode-indicator { - animation: pulse-warning 2s infinite; - box-shadow: 0 0 10px rgba(245, 158, 11, 0.5); -} - -@keyframes pulse-warning { - 0% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.7); } - 70% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); } - 100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); } -} - -/* Verbesserter Info-Panel */ -.info-panel { - transition: all 0.3s ease; -} - -.info-title { - position: relative; -} - -.info-title::after { - content: ''; - position: absolute; - bottom: -5px; - left: 0; - width: 50px; - height: 3px; - background: linear-gradient(90deg, #60a5fa, #8b5cf6); - border-radius: 2px; -} - -.info-content { - max-height: 300px; - overflow-y: auto; -} - -/* Anpassungen für kleinere Bildschirme */ -@media (max-width: 768px) { - .crud-panel { - bottom: 1rem; - left: 1rem; - right: 1rem; - transform: none; - justify-content: space-around; - } - - .info-panel { - left: 1rem; - width: calc(100% - 2rem); - max-width: none; - } - - .control-panel { - right: 1rem; - } } \ No newline at end of file diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js index 2e97f87..2541013 100644 --- a/static/js/update_mindmap.js +++ b/static/js/update_mindmap.js @@ -820,11 +820,11 @@ function showUINotification(message, type = 'info', duration = 3000) { closeButton.onclick = () => { notification.style.opacity = '0'; notification.style.transform = 'translateY(-20px)'; - setTimeout(() => { + setTimeout(() => { if (notification.parentNode === container) { container.removeChild(notification); - } - }, 300); + } + }, 300); }; notification.appendChild(closeButton); @@ -832,23 +832,23 @@ function showUINotification(message, type = 'info', duration = 3000) { container.appendChild(notification); // Animation starten - setTimeout(() => { + setTimeout(() => { notification.style.opacity = '1'; notification.style.transform = 'translateY(0)'; }, 10); // Automatisch ausblenden, wenn keine Dauer von 0 übergeben wurde if (duration > 0) { - setTimeout(() => { + setTimeout(() => { if (notification.parentNode === container) { notification.style.opacity = '0'; notification.style.transform = 'translateY(-20px)'; setTimeout(() => { if (notification.parentNode === container) { container.removeChild(notification); - } - }, 300); - } + } + }, 300); + } }, duration); } } @@ -968,279 +968,194 @@ function handleEditingAction(action, nodeId) { } // Funktion zum Hinzufügen eines neuen Knotens -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; +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 eines Knotens +// 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(); - // 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)'; - + // Generiere die Kategorie-Optionen + let categoryOptions = ''; + for (const category in mindmapConfig.categories) { + categoryOptions += ``; + } + + // Erstelle das Formular dialog.innerHTML = ` -

Knoten bearbeiten

-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
+

Knoten bearbeiten

+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
`; - + document.body.appendChild(dialog); - - // Event-Listener - document.getElementById('cancel-edit').addEventListener('click', () => { + + // Event-Listener für den Abbrechen-Button + dialog.querySelector('button.cancel').addEventListener('click', function() { document.body.removeChild(dialog); + document.body.removeChild(backdrop); }); - - document.getElementById('save-edit').addEventListener('click', () => { - const name = document.getElementById('node-name').value || 'Unbenannter Knoten'; - const description = document.getElementById('node-description').value || ''; + + // 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 color = document.getElementById('node-color').value; - - // Knoten aktualisieren + 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({ - label: name, - description: description, - category: category, - color: color + ...data, + label, + category, + description, + hasChildren, + color }); - + + // Schließe den Dialog document.body.removeChild(dialog); - showUINotification('Knoten wurde aktualisiert!', 'success'); - }); - - // Kategorie-Auswahl mit Farben verknüpfen - document.getElementById('node-category').addEventListener('change', (e) => { - const category = e.target.value; - let color = '#9F7AEA'; // Standardfarbe + document.body.removeChild(backdrop); - 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; + // Benachrichtigung anzeigen + showUINotification('Knoten wurde aktualisiert', 'success'); }); } -// Funktion zum Löschen eines Knotens -function deleteNode(node) { +// Funktion zum Anzeigen des Kontext-Menüs für einen Knoten +function showNodeContextMenu(node, position) { if (!node) return; - 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'); + // Entferne existierende Kontext-Menüs + const existingMenu = document.querySelector('.context-menu'); + if (existingMenu) { + existingMenu.parentNode.removeChild(existingMenu); } -} - -// Kontextmenü für Knoten anzeigen -function showNodeContextMenu(node, position) { - // Entferne vorhandene Kontextmenüs - removeContextMenus(); - const contextMenu = document.createElement('div'); - contextMenu.className = 'context-menu'; - contextMenu.style.left = `${position.x}px`; - contextMenu.style.top = `${position.y}px`; + // 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`; - contextMenu.innerHTML = ` + // Menü-Einträge + menu.innerHTML = `
- Bearbeiten + + + + + Bearbeiten
-
- Unterknoten hinzufügen +
+ + + + + + Verbindung erstellen
- Löschen + + + + + Löschen
`; - document.body.appendChild(contextMenu); + document.body.appendChild(menu); - // Event-Listener für Menüpunkte - contextMenu.querySelectorAll('.context-menu-item').forEach(item => { + // Event-Listener für Menü-Einträge + menu.querySelectorAll('.context-menu-item').forEach(item => { item.addEventListener('click', function() { const action = this.getAttribute('data-action'); @@ -1248,233 +1163,358 @@ function showNodeContextMenu(node, position) { case 'edit': editNodeProperties(node); break; - case 'add-child': - addNewNode(window.cy, node); + case 'connect': + startEdgeCreation(node); break; case 'delete': deleteNode(node); break; } - removeContextMenus(); + // Entferne das Menü + document.body.removeChild(menu); }); }); - // Event-Listener zum Schließen des Menüs + // Klick außerhalb des Menüs schließt es document.addEventListener('click', function closeMenu(e) { - if (!contextMenu.contains(e.target)) { - removeContextMenus(); + if (!menu.contains(e.target)) { + if (document.body.contains(menu)) { + document.body.removeChild(menu); + } document.removeEventListener('click', closeMenu); } }); } -// Funktion zum Hinzufügen eines Knotens an einer bestimmten Position (Rechtsklick) +// Funktion zum Anzeigen des Menüs zum Hinzufügen eines Knotens function showAddNodeMenu(position) { - // Entferne vorhandene Kontextmenüs - removeContextMenus(); + // Entferne existierende Kontext-Menüs + const existingMenu = document.querySelector('.context-menu'); + if (existingMenu) { + existingMenu.parentNode.removeChild(existingMenu); + } - const contextMenu = document.createElement('div'); - contextMenu.className = 'context-menu'; - contextMenu.style.left = `${position.x}px`; - contextMenu.style.top = `${position.y}px`; + // 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`; - contextMenu.innerHTML = ` -
- Knoten hinzufügen -
+ // 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(contextMenu); + document.body.appendChild(menu); - // Event-Listener für Menüpunkte - contextMenu.querySelectorAll('.context-menu-item').forEach(item => { + // Event-Listener für Kategorie-Einträge + menu.querySelectorAll('.context-menu-item').forEach(item => { item.addEventListener('click', function() { - const action = this.getAttribute('data-action'); + const category = this.getAttribute('data-category'); - 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); - } + // Füge einen neuen Knoten an der Position hinzu + addNewNodeAtPosition(cy, position, category); - removeContextMenus(); + // Entferne das Menü + document.body.removeChild(menu); }); }); - // Event-Listener zum Schließen des Menüs + // Klick außerhalb des Menüs schließt es document.addEventListener('click', function closeMenu(e) { - if (!contextMenu.contains(e.target)) { - removeContextMenus(); + if (!menu.contains(e.target)) { + if (document.body.contains(menu)) { + document.body.removeChild(menu); + } document.removeEventListener('click', closeMenu); } }); } -// Hilfsfunktion zum Entfernen aller Kontextmenüs -function removeContextMenus() { - document.querySelectorAll('.context-menu').forEach(menu => { - menu.remove(); +// 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 Aktivieren des Kanten-Erstellungsmodus -function enableEdgeCreationMode(cy) { - // Status anzeigen - showUINotification('Bitte wählen Sie den Startknoten für die Verbindung', 'info'); +// Funktion zum Starten der Erstellung einer Kante +function startEdgeCreation(sourceNode) { + if (!sourceNode) return; - // Cursor-Stil ändern - document.body.style.cursor = 'crosshair'; + 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'); - // Event-Handler für die Knotenauswahl - const nodeSelectHandler = function(event) { - // Ersten Knoten auswählen - const sourceNode = event.target; + // 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; - // Status aktualisieren - showUINotification('Wählen Sie jetzt den Zielknoten für die Verbindung', 'info'); + // Ignoriere, wenn es der gleiche Knoten ist + if (targetNode.id() === sourceNode.id()) { + showUINotification('Quell- und Zielknoten dürfen nicht identisch sein', 'warning'); + finishEdgeCreation(); + return; + } - // 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(); - } - }; + // 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; + } - // 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'); + // 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-Handler für den ersten Knoten - cy.on('tap', 'node', nodeSelectHandler); + // Event-Listener für Abbruch durch Klick auf leeren Bereich + cy.once('tap', function(evt) { + if (evt.target === cy) { + showUINotification('Kantenerstellung abgebrochen', 'info'); + finishEdgeCreation(); + } + }); - // Funktion zum Beenden des Kantenerstellungsmodus function finishEdgeCreation() { - document.body.style.cursor = ''; + sourceNode.removeClass('edge-source'); cy.container().classList.remove('edge-creation-mode'); } +} + +// Funktion zum Aktivieren des Kantenerstellungsmodus +function enableEdgeCreationMode(cy) { + if (!cy) return; - // Abbrechen durch Klick außerhalb - cy.on('tap', function(event) { - if (event.target === cy) { - cy.off('tap', 'node', nodeSelectHandler); - finishEdgeCreation(); - showUINotification('Verbindungserstellung abgebrochen', 'info'); + // 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 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; +// 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 = `