Refactor mindmap visualization and enhance user authentication UI: Implement API calls to load mindmap data dynamically, process hierarchical data into nodes and links, and improve error handling. Update login and registration templates for a modern design with enhanced validation and user experience. Remove obsolete network background images.

This commit is contained in:
2025-04-27 07:08:38 +02:00
parent 0705ecce59
commit 11ab15127c
6 changed files with 395 additions and 226 deletions

View File

@@ -282,21 +282,25 @@ class MindMapVisualization {
// Zeige Lade-Animation
this.showLoading();
// Demo-Logik: Verwende direkt die Standardknoten
this.nodes = this.defaultNodes;
this.links = this.defaultLinks;
// API-Aufruf durchführen, um die Kategorien und ihre Knoten zu laden
const response = await fetch('/api/mindmap/public');
if (!response.ok) {
throw new Error('API-Fehler: ' + response.statusText);
}
// Simuliere einen API-Aufruf (in einer echten Anwendung würde hier ein Fetch stehen)
await new Promise(resolve => setTimeout(resolve, 1000));
const data = await response.json();
console.log('Geladene Mindmap-Daten:', data);
// Verarbeite die hierarchischen Daten in flache Knoten und Links
const processed = this.processApiData(data);
this.nodes = processed.nodes;
this.links = processed.links;
// Visualisierung aktualisieren
this.updateVisualization();
// Lade-Animation ausblenden
this.hideLoading();
// Zufällige Knoten pulsieren lassen
this.pulseRandomNodes();
} catch (error) {
console.error('Fehler beim Laden der Mindmap-Daten:', error);
@@ -304,64 +308,140 @@ class MindMapVisualization {
this.nodes = this.defaultNodes;
this.links = this.defaultLinks;
// Fehler anzeigen
this.showError('Mindmap-Daten konnten nicht geladen werden. Verwende Standarddaten.');
// Visualisierung auch im Fehlerfall aktualisieren
this.updateVisualization();
this.hideLoading();
}
}
// Startet ein zufälliges Pulsen von Knoten für visuelle Aufmerksamkeit
pulseRandomNodes() {
// Zufälligen Knoten auswählen
const randomNode = () => {
const randomIndex = Math.floor(Math.random() * this.nodes.length);
return this.nodes[randomIndex];
// Verarbeitet die API-Daten in das benötigte Format
processApiData(apiData) {
// Erstelle einen Root-Knoten, der alle Kategorien verbindet
const rootNode = {
id: "root",
name: "Wissen",
description: "Zentrale Wissensbasis",
thought_count: 0
};
// Initiales Pulsen starten
const initialPulse = () => {
const node = randomNode();
this.pulseNode(node);
let nodes = [rootNode];
let links = [];
// Für jede Kategorie Knoten und Verbindungen erstellen
apiData.forEach(category => {
// Kategorie als Knoten hinzufügen
const categoryNode = {
id: `category_${category.id}`,
name: category.name,
description: category.description,
color_code: category.color_code,
icon: category.icon,
thought_count: 0,
type: 'category'
};
// Nächstes Pulsen in 3-7 Sekunden
setTimeout(() => {
const nextNode = randomNode();
this.pulseNode(nextNode);
// Regelmäßig wiederholen
setInterval(() => {
const pulseNode = randomNode();
this.pulseNode(pulseNode);
}, 5000 + Math.random() * 5000);
}, 3000 + Math.random() * 4000);
};
nodes.push(categoryNode);
// Mit Root-Knoten verbinden
links.push({
source: "root",
target: categoryNode.id
});
// Alle Knoten aus dieser Kategorie hinzufügen
if (category.nodes && category.nodes.length > 0) {
category.nodes.forEach(node => {
// Zähle die Gedanken für die Kategorie
categoryNode.thought_count += node.thought_count || 0;
const mindmapNode = {
id: `node_${node.id}`,
name: node.name,
description: node.description || '',
color_code: node.color_code || category.color_code,
thought_count: node.thought_count || 0,
type: 'node',
categoryId: category.id
};
nodes.push(mindmapNode);
// Mit Kategorie-Knoten verbinden
links.push({
source: categoryNode.id,
target: mindmapNode.id
});
});
}
// Rekursiv Unterkategorien verarbeiten
if (category.children && category.children.length > 0) {
this.processSubcategories(category.children, nodes, links, categoryNode.id);
}
});
// Verzögertes Starten nach vollständigem Laden
setTimeout(initialPulse, 1000);
// Root-Knoten-Gedankenzähler aktualisieren
rootNode.thought_count = nodes.reduce((sum, node) => sum + (node.thought_count || 0), 0);
return { nodes, links };
}
// Lässt einen Knoten pulsieren für visuelle Hervorhebung
pulseNode(node) {
if (!this.nodeElements) return;
const nodeElement = this.nodeElements.filter(d => d.id === node.id);
if (nodeElement.size() > 0) {
const circle = nodeElement.select('circle');
// Verarbeitet Unterkategorien rekursiv
processSubcategories(subcategories, nodes, links, parentId) {
subcategories.forEach(category => {
// Kategorie als Knoten hinzufügen
const categoryNode = {
id: `category_${category.id}`,
name: category.name,
description: category.description,
color_code: category.color_code,
icon: category.icon,
thought_count: 0,
type: 'subcategory'
};
// Speichern des ursprünglichen Radius
const originalRadius = circle.attr('r');
nodes.push(categoryNode);
// Animiertes Pulsieren
circle.transition()
.duration(600)
.attr('r', originalRadius * 1.3)
.attr('filter', 'url(#pulse-effect)')
.transition()
.duration(600)
.attr('r', originalRadius)
.attr('filter', 'url(#glass-effect)');
}
// Mit Eltern-Kategorie verbinden
links.push({
source: parentId,
target: categoryNode.id
});
// Alle Knoten aus dieser Kategorie hinzufügen
if (category.nodes && category.nodes.length > 0) {
category.nodes.forEach(node => {
// Zähle die Gedanken für die Kategorie
categoryNode.thought_count += node.thought_count || 0;
const mindmapNode = {
id: `node_${node.id}`,
name: node.name,
description: node.description || '',
color_code: node.color_code || category.color_code,
thought_count: node.thought_count || 0,
type: 'node',
categoryId: category.id
};
nodes.push(mindmapNode);
// Mit Kategorie-Knoten verbinden
links.push({
source: categoryNode.id,
target: mindmapNode.id
});
});
}
// Rekursiv Unterkategorien verarbeiten
if (category.children && category.children.length > 0) {
this.processSubcategories(category.children, nodes, links, categoryNode.id);
}
});
}
// Zeigt den Ladebildschirm an
@@ -586,11 +666,20 @@ class MindMapVisualization {
}
}
// Farbe basierend auf Knotentyp erhalten
// Bestimmt die Farbe eines Knotens basierend auf seinem Typ oder direkt angegebener Farbe
getNodeColor(node) {
// Verwende die ID als Typ, falls vorhanden
const nodeType = node.id.toLowerCase();
return this.colorPalette[nodeType] || this.colorPalette.default;
// Direkt angegebene Farbe verwenden, wenn vorhanden
if (node.color_code) {
return node.color_code;
}
// Kategorietyp-basierte Färbung
if (node.type === 'category' || node.type === 'subcategory') {
return this.colorPalette.root;
}
// Fallback für verschiedene Knotentypen
return this.colorPalette[node.id] || this.colorPalette.default;
}
// Aktualisiert die Positionen in jedem Simulationsschritt
@@ -1031,7 +1120,7 @@ class MindMapVisualization {
// Verzögerung für Animation
setTimeout(() => {
// API-Aufruf simulieren (später durch echten Aufruf ersetzen)
// API-Aufruf für echte Daten aus der Datenbank
this.fetchThoughtsForNode(node.id)
.then(thoughts => {
// Ladeanimation ausblenden
@@ -1056,6 +1145,128 @@ class MindMapVisualization {
}, 600); // Verzögerung für bessere UX
}
// Holt Gedanken für einen Knoten aus der Datenbank
async fetchThoughtsForNode(nodeId) {
try {
// Extrahiere die tatsächliche ID aus dem nodeId Format (z.B. "node_123" oder "category_456")
const id = nodeId.toString().split('_')[1];
if (!id) {
console.warn('Ungültige Node-ID: ', nodeId);
return [];
}
// API-Aufruf an den entsprechenden Endpunkt
const response = await fetch(`/api/nodes/${id}/thoughts`);
if (!response.ok) {
throw new Error(`API-Fehler: ${response.statusText}`);
}
const thoughts = await response.json();
console.log('Geladene Gedanken für Knoten:', thoughts);
return thoughts;
} catch (error) {
console.error('Fehler beim Laden der Gedanken für Knoten:', error);
return [];
}
}
// Rendert die Gedanken in der UI
renderThoughts(thoughts, container) {
if (!container) return;
container.innerHTML = '';
thoughts.forEach(thought => {
const thoughtCard = document.createElement('div');
thoughtCard.className = 'thought-card';
thoughtCard.setAttribute('data-id', thought.id);
const cardColor = thought.color_code || this.colorPalette.default;
thoughtCard.innerHTML = `
<div class="thought-card-header" style="border-left: 4px solid ${cardColor}">
<h3 class="thought-title">${thought.title}</h3>
<div class="thought-meta">
<span class="thought-date">${new Date(thought.created_at).toLocaleDateString('de-DE')}</span>
${thought.author ? `<span class="thought-author">von ${thought.author.username}</span>` : ''}
</div>
</div>
<div class="thought-content">
<p>${thought.abstract || thought.content.substring(0, 150) + '...'}</p>
</div>
<div class="thought-footer">
<div class="thought-keywords">
${thought.keywords ? thought.keywords.split(',').map(kw =>
`<span class="keyword">${kw.trim()}</span>`).join('') : ''}
</div>
<a href="/thoughts/${thought.id}" class="thought-link">Mehr lesen →</a>
</div>
`;
// Event-Listener für Klick auf Gedanken
thoughtCard.addEventListener('click', (e) => {
// Verhindern, dass der Link-Klick den Kartenklick auslöst
if (e.target.tagName === 'A') return;
window.location.href = `/thoughts/${thought.id}`;
});
container.appendChild(thoughtCard);
});
}
// Rendert eine Leermeldung, wenn keine Gedanken vorhanden sind
renderEmptyThoughts(container, node) {
if (!container) return;
const emptyState = document.createElement('div');
emptyState.className = 'empty-thoughts-state';
emptyState.innerHTML = `
<div class="empty-icon">
<i class="fas fa-lightbulb"></i>
</div>
<h3>Keine Gedanken verknüpft</h3>
<p>Zu "${node.name}" sind noch keine Gedanken verknüpft.</p>
<div class="empty-actions">
<a href="/add-thought?node=${node.id}" class="btn btn-primary">
<i class="fas fa-plus-circle"></i> Gedanken hinzufügen
</a>
</div>
`;
container.appendChild(emptyState);
}
// Rendert einen Fehlerzustand
renderErrorState(container) {
if (!container) return;
const errorState = document.createElement('div');
errorState.className = 'error-thoughts-state';
errorState.innerHTML = `
<div class="error-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<h3>Fehler beim Laden</h3>
<p>Die Gedanken konnten nicht geladen werden. Bitte versuche es später erneut.</p>
<button class="btn btn-secondary retry-button">
<i class="fas fa-redo"></i> Erneut versuchen
</button>
`;
// Event-Listener für Retry-Button
const retryButton = errorState.querySelector('.retry-button');
if (retryButton && this.selectedNode) {
retryButton.addEventListener('click', () => {
this.loadThoughtsForNode(this.selectedNode);
});
}
container.appendChild(errorState);
}
// Zentriert einen Knoten in der Ansicht
centerNodeInView(node) {
// Sanfter Übergang zur Knotenzentrierüng