From 5793902e47b63b6deb4729af0b4ca5c667e0c9eb Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Wed, 14 May 2025 13:56:11 +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 --- static/css/src/mindmap.css | 165 ------- static/js/update_mindmap.js | 891 ++++++++++++++++++------------------ templates/mindmap.html | 251 +++++++++- 3 files changed, 689 insertions(+), 618 deletions(-) delete mode 100644 static/css/src/mindmap.css diff --git a/static/css/src/mindmap.css b/static/css/src/mindmap.css deleted file mode 100644 index 99bb762..0000000 --- a/static/css/src/mindmap.css +++ /dev/null @@ -1,165 +0,0 @@ -/** - * Mindmap Hauptkomponenten Styling - */ - -/* Toast-Container und Benachrichtigungen */ -#toast-container { - position: fixed; - top: 1rem; - right: 1rem; - z-index: 9999; - display: flex; - flex-direction: column; - gap: 0.5rem; - max-width: 350px; -} - -.toast-message { - transition: all 0.3s ease; - overflow: hidden; -} - -.toast-message.translate-x-full { - transform: translateX(100%); -} - -/* Flash-Nachrichten */ -.flash-message { - position: fixed; - top: 1rem; - left: 50%; - transform: translateX(-50%) translateY(-100%); - padding: 0.75rem 1.5rem; - border-radius: 0.5rem; - background-color: rgba(30, 41, 59, 0.95); - color: white; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); - z-index: 9999; - transition: all 0.3s ease; -} - -.flash-message.visible { - transform: translateX(-50%) translateY(0); -} - -.flash-message.info { - background-color: rgba(59, 130, 246, 0.95); -} - -.flash-message.success { - background-color: rgba(16, 185, 129, 0.95); -} - -.flash-message.warning { - background-color: rgba(245, 158, 11, 0.95); -} - -.flash-message.error { - background-color: rgba(239, 68, 68, 0.95); -} - -/* Zoom-Steuerungen */ -.control-panel { - position: absolute; - right: 2rem; - top: 50%; - transform: translateY(-50%); - background: rgba(15, 23, 42, 0.9); - border-radius: 1rem; - padding: 1.5rem; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); - z-index: 10; - border: 1px solid rgba(255, 255, 255, 0.1); - backdrop-filter: blur(8px); -} - -.control-button { - display: flex; - align-items: center; - padding: 0.75rem 1rem; - margin: 0.5rem 0; - background: rgba(255, 255, 255, 0.1); - border: none; - border-radius: 0.5rem; - color: #fff; - cursor: pointer; - transition: all 0.3s ease; -} - -.control-button:hover { - background: rgba(255, 255, 255, 0.2); - transform: translateX(-5px); -} - -.control-button i { - margin-right: 0.75rem; -} - -/* Info-Panel */ -.info-panel { - position: absolute; - left: 2rem; - top: 50%; - transform: translateY(-50%) translateX(-20px); - opacity: 0; - background: rgba(15, 23, 42, 0.9); - border-radius: 1rem; - padding: 1.5rem; - width: 300px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); - z-index: 10; - border: 1px solid rgba(255, 255, 255, 0.1); - transition: all 0.3s ease; - backdrop-filter: blur(8px); -} - -.info-panel.visible { - opacity: 1; - transform: translateY(-50%) translateX(0); -} - -.info-title { - font-size: 1.25rem; - font-weight: 600; - color: #fff; - margin-bottom: 1rem; - padding-bottom: 0.5rem; - border-bottom: 1px solid rgba(255, 255, 255, 0.1); -} - -.info-content { - color: rgba(255, 255, 255, 0.8); - font-size: 0.95rem; - line-height: 1.6; -} - -/* Kategorie-Legende */ -.category-legend { - position: absolute; - bottom: 2rem; - left: 50%; - transform: translateX(-50%); - background: rgba(15, 23, 42, 0.9); - border-radius: 1rem; - padding: 1rem 2rem; - display: flex; - gap: 1.5rem; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); - z-index: 10; - border: 1px solid rgba(255, 255, 255, 0.1); - backdrop-filter: blur(8px); -} - -.category-item { - display: flex; - align-items: center; - color: rgba(255, 255, 255, 0.8); - font-size: 0.9rem; -} - -.category-color { - width: 12px; - height: 12px; - border-radius: 50%; - margin-right: 0.5rem; -} \ No newline at end of file diff --git a/static/js/update_mindmap.js b/static/js/update_mindmap.js index 4aafd1c..2541013 100644 --- a/static/js/update_mindmap.js +++ b/static/js/update_mindmap.js @@ -182,117 +182,6 @@ async function loadMindmapData(nodeId = null) { } } -/** - * Implementiert Zoomfunktionalität für die Mindmap - * @param {Object} cy - Cytoscape-Instanz - */ -function implementZoomFunctions(cy) { - if (!cy) { - console.error('Cytoscape-Instanz nicht gefunden!'); - return; - } - - // Vergrößern-Button - const zoomInButton = document.getElementById('zoomIn'); - if (zoomInButton) { - zoomInButton.addEventListener('click', function() { - cy.zoom(cy.zoom() * 1.2); - cy.center(); - showUINotification('Ansicht vergrößert', 'info', 1500); - }); - } - - // Verkleinern-Button - const zoomOutButton = document.getElementById('zoomOut'); - if (zoomOutButton) { - zoomOutButton.addEventListener('click', function() { - cy.zoom(cy.zoom() * 0.8); - cy.center(); - showUINotification('Ansicht verkleinert', 'info', 1500); - }); - } - - // Zurücksetzen-Button - const resetViewButton = document.getElementById('resetView'); - if (resetViewButton) { - resetViewButton.addEventListener('click', function() { - cy.fit(); - cy.center(); - showUINotification('Ansicht zurückgesetzt', 'info', 1500); - }); - } - - // Legende-Button - const toggleLegendButton = document.getElementById('toggleLegend'); - const categoryLegend = document.getElementById('categoryLegend'); - - if (toggleLegendButton && categoryLegend) { - toggleLegendButton.addEventListener('click', function() { - if (categoryLegend.style.display === 'none') { - categoryLegend.style.display = 'flex'; - showUINotification('Legende angezeigt', 'info', 1500); - } else { - categoryLegend.style.display = 'none'; - showUINotification('Legende ausgeblendet', 'info', 1500); - } - }); - } -} - -/** - * Funktion zum Starten der Mindmap-Anwendung - */ -async function startMindmapApp() { - try { - const loader = document.getElementById('loader'); - const statusMessage = document.getElementById('statusMessage'); - const cyContainer = document.getElementById('cy'); - - if (!cyContainer) { - throw new Error('Cytoscape-Container nicht gefunden'); - } - - // Anzeigen der Ladeanzeige - if (loader) loader.style.display = 'block'; - if (statusMessage) { - statusMessage.textContent = 'Lade Mindmap...'; - statusMessage.style.display = 'block'; - } - - // Initialisieren der Mindmap - const cy = await initializeMindmap(); - window.cy = cy; // Speichern für globalen Zugriff - - // Zoom-Funktionen und Legendensteuerung implementieren - implementZoomFunctions(cy); - - // Verstecken der Ladeanzeige - if (loader) loader.style.display = 'none'; - if (statusMessage) statusMessage.style.display = 'none'; - - return cy; - } catch (error) { - console.error('Fehler beim Starten der Mindmap-Anwendung:', error); - const statusMessage = document.getElementById('statusMessage'); - - if (statusMessage) { - statusMessage.textContent = 'Fehler beim Laden der Mindmap: ' + error.message; - statusMessage.style.display = 'block'; - } - - const loader = document.getElementById('loader'); - if (loader) loader.style.display = 'none'; - - showUINotification('Fehler beim Laden der Mindmap: ' + error.message, 'error'); - } -} - -// Eventlistener für DOM-Ready -document.addEventListener('DOMContentLoaded', function() { - console.log('DOM vollständig geladen, starte Mindmap-Anwendung'); - startMindmapApp(); -}); - // Funktion zum Initialisieren der Mindmap async function initializeMindmap() { try { @@ -309,138 +198,179 @@ async function initializeMindmap() { label: node.name, category: node.category, description: node.description, - color: getCategoryColor(node.category) + hasChildren: node.has_children, + expanded: false, + color: node.color_code, + fontColor: '#ffffff', + fontSize: node.is_center ? 20 : 16 } })), // Kanten ...data.edges.map(edge => ({ data: { - id: edge.id, source: edge.source, target: edge.target, - strength: edge.weight || 1.0 + strength: edge.strength || 0.5 } })) ]; - // Cytoscape-Container - const cyContainer = document.getElementById('cy'); - if (!cyContainer) { - throw new Error('Cytoscape-Container nicht gefunden'); + // Bestehende Cytoscape-Instanz entfernen, falls vorhanden + if (window.cy && typeof window.cy.destroy === 'function') { + window.cy.destroy(); } - // Cytoscape initialisieren - const cy = cytoscape({ + const cyContainer = document.getElementById('cy'); + if (!cyContainer) { + throw new Error('Mindmap-Container #cy nicht gefunden!'); + } + + window.cy = cytoscape({ container: cyContainer, elements: elements, style: [ - // Knoten-Styling { selector: 'node', style: mindmapStyles.node.base }, - // Kanten-Styling { - selector: 'edge', - style: mindmapStyles.edge.base + selector: 'node[isCenter]', + style: mindmapStyles.node.center }, - // Ausgewählte Knoten { selector: 'node:selected', style: mindmapStyles.node.selected + }, + { + selector: 'edge', + style: mindmapStyles.edge.base } ], layout: mindmapStyles.layout.base }); - // Event-Listener für Knoten-Interaktionen - cy.on('tap', 'node', function(evt) { - const node = evt.target; - showNodeInfo(node); + // Füge neuronale Eigenschaften zu allen Knoten hinzu + cy.nodes().forEach(node => { + const data = node.data(); + // Verwende mindmapConfig für Kategorie-Farben oder einen Standardwert + const categoryColor = data.category && mindmapConfig.categories[data.category] + ? mindmapConfig.categories[data.category].color + : '#60a5fa'; + + node.data({ + ...data, + neuronSize: data.neuronSize || 8, + neuronActivity: data.neuronActivity || 0.8, + refractionPeriod: Math.random() * 300 + 700, + threshold: Math.random() * 0.3 + 0.6, + lastFired: 0, + color: data.color || categoryColor + }); }); - // UI aktualisieren - document.getElementById('loader').style.display = 'none'; - document.getElementById('statusMessage').style.display = 'none'; + // Füge synaptische Eigenschaften zu allen Kanten hinzu + cy.edges().forEach(edge => { + const data = edge.data(); + edge.data({ + ...data, + strength: data.strength || 0.5, + conductionVelocity: Math.random() * 0.5 + 0.3, + latency: Math.random() * 100 + 50 + }); + }); - // Neurales Design anwenden - initializeNeuralDesign(cy); + // Event-Listener für Knoten-Klicks + cy.on('tap', 'node', async function(evt) { + const node = evt.target; + console.log('Node clicked:', node.id(), 'hasChildren:', node.data('hasChildren'), 'expanded:', node.data('expanded')); + + if (node.data('hasChildren') && !node.data('expanded')) { + await loadSubthemes(node); + } + }); - // Gebe die Cytoscape-Instanz zurück - return cy; - } catch (error) { - console.error('Fehler beim Initialisieren der Mindmap:', error); - document.getElementById('statusMessage').textContent = 'Fehler: ' + error.message; - document.getElementById('statusMessage').style.display = 'block'; - document.getElementById('loader').style.display = 'none'; + // Layout ausführen + cy.layout(mindmapStyles.layout.base).run(); - showUINotification('Fehler beim Initialisieren der Mindmap: ' + error.message, 'error'); - throw error; + // Starte neuronale Aktivitätssimulation + startNeuralActivitySimulation(cy); + + // Mindmap mit echten Daten befüllen (Styles, Farben etc.) + updateMindmap(); + + return true; + } catch (error) { + console.error('Fehler bei der Mindmap-Initialisierung:', error); + showUINotification({ + error: 'Mindmap konnte nicht initialisiert werden', + details: error.message + }, 'error'); + return false; } } -/** - * Gibt die Farbe für eine Kategorie zurück - * @param {string} category - Die Kategorie - * @returns {string} - Die Farbe als Hex-Code - */ -function getCategoryColor(category) { - const categoryMap = { - 'Philosophie': '#9F7AEA', // Violett - 'Wissenschaft': '#60A5FA', // Blau - 'Technologie': '#10B981', // Grün - 'Künste': '#F59E0B', // Orange - 'Psychologie': '#EF4444' // Rot - }; +// Warte bis DOM geladen ist +document.addEventListener('DOMContentLoaded', function() { + console.log('DOMContentLoaded Event ausgelöst'); - return categoryMap[category] || '#8B5CF6'; // Standardfarbe, wenn Kategorie nicht gefunden -} + // Prüfe, ob der Container existiert + const cyContainer = document.getElementById('cy'); + console.log('Container gefunden:', cyContainer); + + if (!cyContainer) { + console.error('Mindmap-Container #cy nicht gefunden!'); + return; + } + + // Prüfe, ob Cytoscape verfügbar ist + if (typeof cytoscape === 'undefined') { + console.error('Cytoscape ist nicht definiert!'); + return; + } + console.log('Cytoscape ist verfügbar'); -/** - * Zeigt Informationen zu einem Knoten an - * @param {Object} node - Der Knoten, dessen Informationen angezeigt werden sollen - */ -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'); - - if (!infoPanel || !infoTitle || !infoContent) return; - - const data = node.data(); - - infoTitle.textContent = data.label || 'Knotendetails'; - - let contentHTML = ` -

Kategorie: ${data.category || 'Nicht kategorisiert'}

-

Beschreibung: ${data.description || 'Keine Beschreibung verfügbar'}

- `; - - infoContent.innerHTML = contentHTML; - infoPanel.classList.add('visible'); -} + // Initialisiere die Mindmap + initializeMindmap() + .then(success => { + if (success) { + console.log('Mindmap wurde erfolgreich initialisiert'); + // Event auslösen, damit andere Scripte reagieren können + document.dispatchEvent(new Event('mindmap-loaded')); + console.log('mindmap-loaded Event ausgelöst'); + } else { + console.error('Mindmap-Initialisierung fehlgeschlagen'); + } + }) + .catch(error => { + console.error('Fehler bei der Mindmap-Initialisierung:', error); + showUINotification({ + error: 'Mindmap konnte nicht initialisiert werden', + details: error.message + }, 'error'); + }); +}); // Funktion zum Initialisieren des neuronalen Designs function initializeNeuralDesign(cy) { - if (!cy) return; - - console.log('Initialisiere neurales Design für Mindmap'); - - // Füge neurale Eigenschaften zu allen Knoten hinzu + // Füge neuronale Eigenschaften zu allen Knoten hinzu cy.nodes().forEach(node => { const data = node.data(); + // Verwende mindmapConfig für Kategorie-Farben oder einen Standardwert + const categoryColor = data.category && mindmapConfig.categories[data.category] + ? mindmapConfig.categories[data.category].color + : '#60a5fa'; + node.data({ ...data, neuronSize: data.neuronSize || 8, neuronActivity: data.neuronActivity || 0.8, refractionPeriod: Math.random() * 300 + 700, threshold: Math.random() * 0.3 + 0.6, - lastFired: 0 + lastFired: 0, + color: data.color || categoryColor }); }); - + // Füge synaptische Eigenschaften zu allen Kanten hinzu cy.edges().forEach(edge => { const data = edge.data(); @@ -451,172 +381,66 @@ function initializeNeuralDesign(cy) { latency: Math.random() * 100 + 50 }); }); - - // Wende neurales Netzwerk-Styling an - applyNeuralNetworkStyle(cy); - - // Starte Simulation der neuronalen Aktivität - startNeuralActivitySimulation(cy); - - return cy; -} -// Funktionen für das neurale Netzwerk-Design -function applyNeuralNetworkStyle(cy) { - if (!cy) return; - - // Animation für pulsierende Knoten - const pulseKeyframes = [ - { scale: 1.0, opacity: 0.8, borderOpacity: 0.6 }, - { scale: 1.05, opacity: 1.0, borderOpacity: 0.9 }, - { scale: 1.0, opacity: 0.8, borderOpacity: 0.6 } - ]; - - // Anwenden auf alle Knoten - cy.nodes().forEach(node => { - const randomDelay = Math.random() * 3000; - - // CSS-Animationen auf die Knoten anwenden - node.style({ - 'border-width': 3, - 'border-opacity': 0.6, - 'background-opacity': 0.8, - 'transition-property': 'background-opacity, border-opacity, width, height', - 'transition-duration': '300ms' - }); - - // Pulseffekt mit zufälligem Delay starten - setTimeout(() => { - node.animate({ - style: pulseKeyframes, - duration: 2000 + Math.random() * 1000, - complete: function() { - // Animation wiederholen - this.loopAnimation = true; - if (this.loopAnimation) { - setTimeout(() => { - applyNeuralNetworkStyle(cy); - }, 100); - } - } - }); - }, randomDelay); - }); -} - -// Simulation der neuronalen Aktivität -function startNeuralActivitySimulation(cy) { - if (!cy) return; - - console.log('Starte Simulation der neuronalen Aktivität'); - let isSimulationRunning = true; - - // Funktion zur Simulation der neuronalen Aktivität - function simulateNeuralActivity() { - if (!isSimulationRunning) return; - - const currentTime = Date.now(); - - // Wähle zufällige Knoten zum Feuern aus - const randomNode = cy.nodes()[Math.floor(Math.random() * cy.nodes().length)]; - if (randomNode) { - fireNeuron(randomNode, { isPrimary: true }, currentTime); - } - - // Wiederholung der Simulation - setTimeout(simulateNeuralActivity, 2000 + Math.random() * 3000); - } - - // Funktion zum "Feuern" eines Neurons - function fireNeuron(node, state, currentTime) { - if (!node) return; - - const data = node.data(); - const lastFired = data.lastFired || 0; - const refractionPeriod = data.refractionPeriod || 1000; - - // Prüfe, ob das Neuron feuerbereit ist (Refraktärzeit vorbei) - if (currentTime - lastFired < refractionPeriod && !state.isPrimary) { - return; // Neuron ist noch in Refraktärphase - } - - // Aktualisiere "Letztes Feuern"-Zeitstempel - node.data('lastFired', currentTime); - - // Visuellen Effekt des Feuerns anzeigen - node.animate({ - style: { - 'background-opacity': 1, - 'border-opacity': 1, - 'border-color': '#f59e42', - 'border-width': 4 + // Wende neuronales Styling an + cy.style() + .selector('node') + .style({ + 'background-color': 'data(color)', + 'label': 'data(label)', + 'color': '#fff', + 'text-background-color': 'rgba(0, 0, 0, 0.7)', + 'text-background-opacity': 0.8, + 'text-background-padding': '4px', + 'text-valign': 'center', + 'text-halign': 'center', + 'font-size': 16, + 'width': 40, + 'height': 40, + 'border-width': 2, + 'border-color': '#fff', + 'border-opacity': 0.8, + 'overlay-padding': 4, + 'z-index': 10, + 'shape': 'ellipse', + 'background-opacity': 0.85, + 'shadow-blur': 15, + 'shadow-color': 'data(color)', + 'shadow-opacity': 0.6, + 'shadow-offset-x': 0, + 'shadow-offset-y': 0 + }) + .selector('edge') + .style({ + 'width': function(ele) { + return ele.data('strength') ? ele.data('strength') * 3 : 1; }, - duration: 300, - complete: function() { - // Zurück zum Normalzustand - node.animate({ - style: { - 'background-opacity': 0.8, - 'border-opacity': 0.6, - 'border-color': '#ffffff', - 'border-width': 3 - }, - duration: 500 - }); - - // Signal an verbundene Knoten weitergeben - propagateSignal(node, currentTime); - } - }); - } - - // Funktion zur Signalausbreitung zu verbundenen Knoten - function propagateSignal(sourceNode, currentTime) { - // Verbundene Kanten finden - const connectedEdges = sourceNode.outgoers('edge'); - - connectedEdges.forEach(edge => { - const targetNode = edge.target(); - const data = edge.data(); - const strength = data.strength || 0.5; - const conductionVelocity = data.conductionVelocity || 0.5; - const latency = data.latency || 100; - - // Kante hervorheben (Signal fließt durch die Kante) - edge.animate({ - style: { - 'line-opacity': 1, - 'width': 3 - }, - duration: 200, - complete: function() { - // Nach einer Verzögerung (Latenz), das Ziel-Neuron feuern lassen - setTimeout(() => { - edge.animate({ - style: { - 'line-opacity': data.strength ? data.strength * 0.6 : 0.4, - 'width': data.strength ? data.strength * 2 : 1 - }, - duration: 300 - }); - - // Ziel-Neuron feuern lassen mit Wahrscheinlichkeit basierend auf Stärke - if (Math.random() < strength) { - fireNeuron(targetNode, { isPrimary: false }, currentTime + latency); - } - }, latency / conductionVelocity); - } - }); - }); - } - - // Starte die Simulation - simulateNeuralActivity(); - - // Funktion zum Stoppen der Simulation - cy.stopNeuralSimulation = function() { - isSimulationRunning = false; - }; + 'curve-style': 'bezier', + 'line-color': function(ele) { + const sourceColor = ele.source().data('color'); + return sourceColor || '#8a8aaa'; + }, + 'line-opacity': function(ele) { + return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; + }, + 'line-style': function(ele) { + const strength = ele.data('strength'); + if (!strength) return 'solid'; + if (strength <= 0.4) return 'dotted'; + if (strength <= 0.6) return 'dashed'; + return 'solid'; + }, + 'target-arrow-shape': 'none', + 'source-endpoint': '0% 50%', + 'target-endpoint': '100% 50%', + 'transition-property': 'line-opacity, width', + 'transition-duration': '0.3s', + 'transition-timing-function': 'ease-in-out' + }) + .update(); + + // Starte neuronale Aktivitätssimulation + startNeuralActivitySimulation(cy); } // Modifiziere die updateMindmap Funktion @@ -743,122 +567,289 @@ function enhanceMindmap() { startNeuralActivitySimulation(cy); } +/** + * Wendet detaillierte neuronale Netzwerkstile auf die Mindmap an + * @param {Object} cy - Cytoscape-Instanz + */ +function applyNeuralNetworkStyle(cy) { + cy.style() + .selector('node') + .style({ + 'label': 'data(label)', + 'text-valign': 'center', + 'text-halign': 'center', + 'color': 'data(fontColor)', + 'text-outline-width': 2, + 'text-outline-color': 'rgba(0,0,0,0.8)', + 'text-outline-opacity': 0.9, + 'font-size': 'data(fontSize)', + 'font-weight': '500', + 'text-margin-y': 8, + 'width': function(ele) { + if (ele.data('isCenter')) return 120; + return 80; + }, + 'height': function(ele) { + if (ele.data('isCenter')) return 120; + return 80; + }, + 'background-color': 'data(color)', + 'background-opacity': 0.9, + 'border-width': 2, + 'border-color': '#ffffff', + 'border-opacity': 0.8, + 'shape': 'ellipse', + 'transition-property': 'background-color, background-opacity, border-width', + 'transition-duration': '0.3s', + 'transition-timing-function': 'ease-in-out' + }) + .selector('edge') + .style({ + 'width': function(ele) { + return ele.data('strength') ? ele.data('strength') * 3 : 1; + }, + 'curve-style': 'bezier', + 'line-color': function(ele) { + const sourceColor = ele.source().data('color'); + return sourceColor || '#8a8aaa'; + }, + 'line-opacity': function(ele) { + return ele.data('strength') ? ele.data('strength') * 0.8 : 0.4; + }, + 'line-style': function(ele) { + const strength = ele.data('strength'); + if (!strength) return 'solid'; + if (strength <= 0.4) return 'dotted'; + if (strength <= 0.6) return 'dashed'; + return 'solid'; + }, + 'target-arrow-shape': 'none', + 'source-endpoint': '0% 50%', + 'target-endpoint': '100% 50%', + 'transition-property': 'line-opacity, width', + 'transition-duration': '0.3s', + 'transition-timing-function': 'ease-in-out' + }) + .update(); +} + +// Vereinfachte neuronale Aktivitätssimulation +function startNeuralActivitySimulation(cy) { + if (window.neuralInterval) clearInterval(window.neuralInterval); + + const nodes = cy.nodes(); + let currentTime = Date.now(); + + function simulateNeuralActivity() { + currentTime = Date.now(); + + nodes.forEach(node => { + const data = node.data(); + const lastFired = data.lastFired || 0; + const timeSinceLastFire = currentTime - lastFired; + + if (timeSinceLastFire > data.refractionPeriod) { + if (Math.random() < data.neuronActivity * 0.1) { + fireNeuron(node, true, currentTime); + } + } + }); + } + + function fireNeuron(node, state, currentTime) { + const data = node.data(); + data.lastFired = currentTime; + + node.style({ + 'background-opacity': 1, + 'border-width': 3 + }); + + setTimeout(() => { + node.style({ + 'background-opacity': 0.9, + 'border-width': 2 + }); + }, 200); + + if (state) { + propagateSignal(node, currentTime); + } + } + + function propagateSignal(sourceNode, currentTime) { + const outgoingEdges = sourceNode.connectedEdges(); + + outgoingEdges.forEach(edge => { + const targetNode = edge.target(); + const edgeData = edge.data(); + const latency = edgeData.latency; + + edge.style({ + 'line-opacity': 0.8, + 'width': edgeData.strength * 3 + }); + + setTimeout(() => { + edge.style({ + 'line-opacity': edgeData.strength * 0.6, + 'width': edgeData.strength * 2 + }); + }, 200); + + setTimeout(() => { + const targetData = targetNode.data(); + const timeSinceLastFire = currentTime - (targetData.lastFired || 0); + + if (timeSinceLastFire > targetData.refractionPeriod) { + const signalStrength = edgeData.strength * + edgeData.conductionVelocity * + sourceNode.data('neuronActivity'); + + if (signalStrength > targetData.threshold) { + fireNeuron(targetNode, true, currentTime + latency); + } + } + }, latency); + }); + } + + window.neuralInterval = setInterval(simulateNeuralActivity, 100); +} + // Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises function showFlash(message, type = 'info') { + const flashContainer = createFlashContainer(); const flash = document.createElement('div'); flash.className = `flash-message ${type}`; - flash.innerHTML = ` -
- - ${message} -
- `; - - document.body.appendChild(flash); - - // Animation zum Einblenden + flash.textContent = message; + flashContainer.appendChild(flash); + document.body.appendChild(flashContainer); + setTimeout(() => { - flash.classList.add('visible'); - }, 10); - - // Automatisches Ausblenden nach 3 Sekunden - setTimeout(() => { - flash.classList.remove('visible'); + flash.classList.add('show'); setTimeout(() => { - document.body.removeChild(flash); - }, 300); - }, 3000); + flash.classList.remove('show'); + setTimeout(() => { + flashContainer.remove(); + }, 300); + }, 3000); + }, 100); } /** - * Zeigt eine UI-Benachrichtigung - * @param {string} message - Die Nachricht - * @param {string} type - Der Typ der Benachrichtigung (info, success, warning, error) - * @param {number} duration - Die Anzeigedauer in Millisekunden + * Zeigt eine Benachrichtigung in der UI an + * @param {string|object} message - Die anzuzeigende Nachricht oder ein Fehlerobjekt + * @param {string} type - Der Typ der Benachrichtigung ('info', 'success', 'warning', 'error') + * @param {number} duration - Die Anzeigedauer in Millisekunden (Standard: 3000) */ function showUINotification(message, type = 'info', duration = 3000) { - // Prüfe, ob Toast-Container existiert, sonst erstellen - let toastContainer = document.getElementById('toast-container'); - - if (!toastContainer) { - toastContainer = document.createElement('div'); - toastContainer.id = 'toast-container'; - toastContainer.className = 'fixed top-4 right-4 z-50 flex flex-col gap-2'; - document.body.appendChild(toastContainer); + // Container erstellen, falls er nicht existiert + let container = document.getElementById('notification-container'); + if (!container) { + container = document.createElement('div'); + container.id = 'notification-container'; + container.style.position = 'fixed'; + container.style.top = '1rem'; + container.style.right = '1rem'; + container.style.zIndex = '1000'; + container.style.maxWidth = '400px'; + document.body.appendChild(container); } - // Erstelle Toast-Element - const toast = document.createElement('div'); - toast.className = 'toast-message transform transition-all duration-300 ease-out translate-x-full'; + // Benachrichtigung erstellen + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + notification.style.padding = '1rem'; + notification.style.marginBottom = '0.5rem'; + notification.style.borderRadius = '0.25rem'; + notification.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)'; + notification.style.position = 'relative'; + notification.style.opacity = '0'; + notification.style.transform = 'translateY(-20px)'; + notification.style.transition = 'all 0.3s ease-in-out'; - // Setze Styling basierend auf Typ - let iconClass = 'fa-info-circle'; - let bgColorClass = 'bg-blue-500'; - - switch (type) { - case 'success': - iconClass = 'fa-check-circle'; - bgColorClass = 'bg-green-500'; - break; - case 'warning': - iconClass = 'fa-exclamation-circle'; - bgColorClass = 'bg-yellow-500'; - break; - case 'error': - iconClass = 'fa-exclamation-triangle'; - bgColorClass = 'bg-red-500'; - break; + // Farben nach Typ + if (type === 'success') { + notification.style.backgroundColor = '#059669'; + notification.style.color = '#ffffff'; + } else if (type === 'error') { + notification.style.backgroundColor = '#DC2626'; + notification.style.color = '#ffffff'; + } else if (type === 'warning') { + notification.style.backgroundColor = '#F59E0B'; + notification.style.color = '#ffffff'; + } else { + notification.style.backgroundColor = '#3B82F6'; + notification.style.color = '#ffffff'; } - // Inhalt des Toasts - toast.innerHTML = ` -
- - ${message} - -
- `; + // Nachrichteninhalt formatieren + let content = ''; - // Füge Toast zum Container hinzu - toastContainer.appendChild(toast); + if (typeof message === 'object' && message !== null) { + // Wenn es ein Fehler-Objekt ist + if (message.error) { + content = message.error; + if (message.details) { + content += `
${message.details}`; + } + } else { + // Versuche, das Objekt zu stringifizieren + try { + content = JSON.stringify(message); + } catch (e) { + content = 'Objekt konnte nicht angezeigt werden'; + } + } + } else { + // String oder andere primitive Typen + content = message; + } - // Animation zum Einblenden - setTimeout(() => { - toast.classList.remove('translate-x-full'); + notification.innerHTML = content; + + // Schließen-Button + const closeButton = document.createElement('span'); + closeButton.innerHTML = '×'; + closeButton.style.position = 'absolute'; + closeButton.style.top = '0.25rem'; + closeButton.style.right = '0.5rem'; + closeButton.style.fontSize = '1.25rem'; + closeButton.style.cursor = 'pointer'; + closeButton.onclick = () => { + notification.style.opacity = '0'; + notification.style.transform = 'translateY(-20px)'; + setTimeout(() => { + if (notification.parentNode === container) { + container.removeChild(notification); + } + }, 300); + }; + notification.appendChild(closeButton); + + // Zur Seite hinzufügen + container.appendChild(notification); + + // Animation starten + setTimeout(() => { + notification.style.opacity = '1'; + notification.style.transform = 'translateY(0)'; }, 10); - // Schließen-Button-Funktionalität - const closeButton = toast.querySelector('button'); - closeButton.addEventListener('click', () => { - removeToast(toast); - }); - - // Automatisches Ausblenden nach der angegebenen Dauer - const timeoutId = setTimeout(() => { - removeToast(toast); - }, duration); - - // Funktion zum Entfernen des Toasts - function removeToast(toastElement) { - // Animation zum Ausblenden - toastElement.classList.add('translate-x-full'); - - // Entfernen nach Abschluss der Animation - setTimeout(() => { - if (toastElement.parentNode === toastContainer) { - toastContainer.removeChild(toastElement); - } - - // Entferne Container, wenn keine Toasts mehr vorhanden sind - if (toastContainer.children.length === 0) { - document.body.removeChild(toastContainer); - } - }, 300); - - // Timeout löschen - clearTimeout(timeoutId); + // Automatisch ausblenden, wenn keine Dauer von 0 übergeben wurde + if (duration > 0) { + setTimeout(() => { + if (notification.parentNode === container) { + notification.style.opacity = '0'; + notification.style.transform = 'translateY(-20px)'; + setTimeout(() => { + if (notification.parentNode === container) { + container.removeChild(notification); + } + }, 300); + } + }, duration); } } diff --git a/templates/mindmap.html b/templates/mindmap.html index 85dcca5..84cbf50 100644 --- a/templates/mindmap.html +++ b/templates/mindmap.html @@ -3,7 +3,6 @@ {% block title %}Interaktive Mindmap{% endblock %} {% block extra_css %} -