Add flash message API and enhance mindmap visualization: Implement a new API endpoint for retrieving flash messages, integrate flash message display in the mindmap visualization, and improve user feedback with dynamic notifications. Update UI elements for better responsiveness and visual appeal, while removing obsolete background image references.

This commit is contained in:
2025-04-27 07:18:32 +02:00
parent 11ab15127c
commit 5372fe220e
9 changed files with 363 additions and 125 deletions

View File

@@ -32,6 +32,9 @@ class MindMapVisualization {
this.tooltipDiv = null;
this.isLoading = true;
// Flash-Nachrichten-Container
this.flashContainer = null;
// Erweiterte Farbpalette für Knotentypen
this.colorPalette = {
'default': '#b38fff',
@@ -57,6 +60,7 @@ class MindMapVisualization {
if (this.container.node()) {
this.init();
this.setupDefaultNodes();
this.setupFlashMessages();
// Sofortige Datenladung
window.setTimeout(() => {
@@ -67,6 +71,183 @@ class MindMapVisualization {
}
}
// Flash-Nachrichten-System einrichten
setupFlashMessages() {
// Flash-Container erstellen, falls er noch nicht existiert
if (!document.getElementById('mindmap-flash-container')) {
this.flashContainer = document.createElement('div');
this.flashContainer.id = 'mindmap-flash-container';
this.flashContainer.className = 'mindmap-flash-container';
this.flashContainer.style.position = 'fixed';
this.flashContainer.style.top = '20px';
this.flashContainer.style.right = '20px';
this.flashContainer.style.zIndex = '1000';
this.flashContainer.style.maxWidth = '350px';
this.flashContainer.style.display = 'flex';
this.flashContainer.style.flexDirection = 'column';
this.flashContainer.style.gap = '10px';
document.body.appendChild(this.flashContainer);
} else {
this.flashContainer = document.getElementById('mindmap-flash-container');
}
// Prüfen, ob Server-seitige Flash-Nachrichten existieren und anzeigen
this.checkForServerFlashMessages();
}
// Prüft auf Server-seitige Flash-Nachrichten
async checkForServerFlashMessages() {
try {
const response = await fetch('/api/get_flash_messages');
if (response.ok) {
const messages = await response.json();
messages.forEach(message => {
this.showFlash(message.message, message.category);
});
}
} catch (err) {
console.error('Fehler beim Abrufen der Flash-Nachrichten:', err);
}
}
// Zeigt eine Flash-Nachricht an
showFlash(message, type = 'info', duration = 5000) {
if (!this.flashContainer) return;
const flashElement = document.createElement('div');
flashElement.className = `mindmap-flash flash-${type}`;
flashElement.style.padding = '12px 18px';
flashElement.style.borderRadius = '8px';
flashElement.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
flashElement.style.display = 'flex';
flashElement.style.alignItems = 'center';
flashElement.style.justifyContent = 'space-between';
flashElement.style.fontSize = '14px';
flashElement.style.fontWeight = '500';
flashElement.style.backdropFilter = 'blur(10px)';
flashElement.style.opacity = '0';
flashElement.style.transform = 'translateY(-20px)';
flashElement.style.transition = 'all 0.3s ease';
// Spezifische Stile je nach Nachrichtentyp
switch(type) {
case 'success':
flashElement.style.backgroundColor = 'rgba(34, 197, 94, 0.9)';
flashElement.style.borderLeft = '5px solid #16a34a';
flashElement.style.color = 'white';
break;
case 'error':
flashElement.style.backgroundColor = 'rgba(239, 68, 68, 0.9)';
flashElement.style.borderLeft = '5px solid #dc2626';
flashElement.style.color = 'white';
break;
case 'warning':
flashElement.style.backgroundColor = 'rgba(245, 158, 11, 0.9)';
flashElement.style.borderLeft = '5px solid #d97706';
flashElement.style.color = 'white';
break;
default: // info
flashElement.style.backgroundColor = 'rgba(59, 130, 246, 0.9)';
flashElement.style.borderLeft = '5px solid #2563eb';
flashElement.style.color = 'white';
}
// Icon je nach Nachrichtentyp
let icon = '';
switch(type) {
case 'success':
icon = '<i class="fas fa-check-circle"></i>';
break;
case 'error':
icon = '<i class="fas fa-exclamation-circle"></i>';
break;
case 'warning':
icon = '<i class="fas fa-exclamation-triangle"></i>';
break;
default:
icon = '<i class="fas fa-info-circle"></i>';
}
// Inhalt der Nachricht mit Icon
const contentWrapper = document.createElement('div');
contentWrapper.style.display = 'flex';
contentWrapper.style.alignItems = 'center';
contentWrapper.style.gap = '12px';
const iconElement = document.createElement('div');
iconElement.className = 'flash-icon';
iconElement.innerHTML = icon;
const textElement = document.createElement('div');
textElement.className = 'flash-text';
textElement.textContent = message;
contentWrapper.appendChild(iconElement);
contentWrapper.appendChild(textElement);
// Schließen-Button
const closeButton = document.createElement('button');
closeButton.className = 'flash-close';
closeButton.innerHTML = '<i class="fas fa-times"></i>';
closeButton.style.background = 'none';
closeButton.style.border = 'none';
closeButton.style.color = 'currentColor';
closeButton.style.cursor = 'pointer';
closeButton.style.marginLeft = '15px';
closeButton.style.padding = '3px';
closeButton.style.fontSize = '14px';
closeButton.style.opacity = '0.7';
closeButton.style.transition = 'opacity 0.2s';
closeButton.addEventListener('mouseover', () => {
closeButton.style.opacity = '1';
});
closeButton.addEventListener('mouseout', () => {
closeButton.style.opacity = '0.7';
});
closeButton.addEventListener('click', () => {
this.removeFlash(flashElement);
});
// Zusammenfügen
flashElement.appendChild(contentWrapper);
flashElement.appendChild(closeButton);
// Zum Container hinzufügen
this.flashContainer.appendChild(flashElement);
// Animation einblenden
setTimeout(() => {
flashElement.style.opacity = '1';
flashElement.style.transform = 'translateY(0)';
}, 10);
// Automatisches Ausblenden nach der angegebenen Zeit
if (duration > 0) {
setTimeout(() => {
this.removeFlash(flashElement);
}, duration);
}
return flashElement;
}
// Entfernt eine Flash-Nachricht mit Animation
removeFlash(flashElement) {
if (!flashElement) return;
flashElement.style.opacity = '0';
flashElement.style.transform = 'translateY(-20px)';
setTimeout(() => {
if (flashElement.parentNode) {
flashElement.parentNode.removeChild(flashElement);
}
}, 300);
}
// Standardknoten als Fallback einrichten, falls die API nicht reagiert
setupDefaultNodes() {
// Basis-Mindmap mit Hauptthemen
@@ -301,6 +482,9 @@ class MindMapVisualization {
// Lade-Animation ausblenden
this.hideLoading();
// Erfolgreiche Ladung melden
this.showFlash('Mindmap-Daten erfolgreich geladen', 'success');
} catch (error) {
console.error('Fehler beim Laden der Mindmap-Daten:', error);
@@ -310,6 +494,7 @@ class MindMapVisualization {
// Fehler anzeigen
this.showError('Mindmap-Daten konnten nicht geladen werden. Verwende Standarddaten.');
this.showFlash('Fehler beim Laden der Mindmap-Daten. Standarddaten werden angezeigt.', 'error');
// Visualisierung auch im Fehlerfall aktualisieren
this.updateVisualization();
@@ -992,6 +1177,9 @@ class MindMapVisualization {
window.onNodeDeselected();
}
// Flash-Nachricht für abgewählten Knoten
this.showFlash('Knotenauswahl aufgehoben', 'info', 2000);
return;
}
@@ -1083,6 +1271,7 @@ class MindMapVisualization {
if (!thoughtContainer || !thoughtsList) {
console.error('Gedanken-Container nicht gefunden');
this.showFlash('Fehler: Gedanken-Container nicht gefunden', 'error');
return;
}
@@ -1118,6 +1307,9 @@ class MindMapVisualization {
thoughtsList.innerHTML = '';
}
// Flash-Nachricht über ausgewählten Knoten
this.showFlash(`Knoten "${node.name}" ausgewählt`, 'info');
// Verzögerung für Animation
setTimeout(() => {
// API-Aufruf für echte Daten aus der Datenbank
@@ -1133,6 +1325,7 @@ class MindMapVisualization {
this.renderThoughts(thoughts, thoughtsList);
} else {
this.renderEmptyThoughts(thoughtsList, node);
this.showFlash(`Keine Gedanken zu "${node.name}" gefunden`, 'warning');
}
})
.catch(error => {
@@ -1141,6 +1334,7 @@ class MindMapVisualization {
loadingIndicator.style.display = 'none';
}
this.renderErrorState(thoughtsList);
this.showFlash('Fehler beim Laden der Gedanken. Bitte versuche es später erneut.', 'error');
});
}, 600); // Verzögerung für bessere UX
}
@@ -1152,6 +1346,7 @@ class MindMapVisualization {
const id = nodeId.toString().split('_')[1];
if (!id) {
console.warn('Ungültige Node-ID: ', nodeId);
this.showFlash('Ungültige Knoten-ID: ' + nodeId, 'warning');
return [];
}
@@ -1164,9 +1359,17 @@ class MindMapVisualization {
const thoughts = await response.json();
console.log('Geladene Gedanken für Knoten:', thoughts);
if (thoughts.length > 0) {
this.showFlash(`${thoughts.length} Gedanken zum Thema geladen`, 'info');
} else {
this.showFlash('Keine Gedanken für diesen Knoten gefunden', 'info');
}
return thoughts;
} catch (error) {
console.error('Fehler beim Laden der Gedanken für Knoten:', error);
this.showFlash('Fehler beim Laden der Gedanken', 'error');
return [];
}
}
@@ -1282,10 +1485,16 @@ class MindMapVisualization {
d3.zoom().transform,
d3.zoomIdentity.translate(x, y).scale(scale)
);
// Flash-Nachricht für Zentrierung
if (node && node.name) {
this.showFlash(`Ansicht auf "${node.name}" zentriert`, 'info', 2000);
}
}
// Fehlermeldung anzeigen
showError(message) {
// Standard-Fehlermeldung als Banner
const errorBanner = d3.select('body').selectAll('.error-banner').data([0]);
const errorEnter = errorBanner.enter()
@@ -1314,12 +1523,18 @@ class MindMapVisualization {
.delay(5000)
.duration(500)
.style('bottom', '-100px');
// Auch als Flash-Nachricht anzeigen
this.showFlash(message, 'error');
}
// Fokussieren auf einen bestimmten Knoten per ID
focusNode(nodeId) {
const targetNode = this.nodes.find(n => n.id === nodeId);
if (!targetNode) return;
if (!targetNode) {
this.showFlash(`Knoten mit ID "${nodeId}" nicht gefunden`, 'error');
return;
}
// Ausgewählten Zustand zurücksetzen
this.selectedNode = null;
@@ -1339,6 +1554,8 @@ class MindMapVisualization {
d3.zoom().transform,
transform
);
this.showFlash(`Fokus auf Knoten "${targetNode.name}" gesetzt`, 'success');
}
}
@@ -1358,6 +1575,8 @@ class MindMapVisualization {
.style('display', 'block')
.style('stroke-opacity', 0.5);
this.showFlash('Suchfilter zurückgesetzt', 'info', 2000);
return;
}
@@ -1393,6 +1612,9 @@ class MindMapVisualization {
// Wenn mehr als ein Knoten gefunden wurde, Simulation mit reduzierter Stärke neu starten
if (matchingNodes.length > 1) {
this.simulation.alpha(0.3).restart();
this.showFlash(`${matchingNodes.length} Knoten für "${searchTerm}" gefunden`, 'success');
} else if (matchingNodes.length === 0) {
this.showFlash(`Keine Knoten für "${searchTerm}" gefunden`, 'warning');
}
}
}