From 11ab15127c34f68f03390b55f3b59befe28a347e Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Sun, 27 Apr 2025 07:08:38 +0200 Subject: [PATCH] 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. --- website/__pycache__/app.cpython-311.pyc | Bin 51172 -> 51172 bytes website/static/mindmap.js | 325 +++++++++++++++++++----- website/static/network-bg.jpg | 7 - website/static/network-bg.svg | 99 -------- website/templates/login.html | 81 +++--- website/templates/register.html | 109 ++++++-- 6 files changed, 395 insertions(+), 226 deletions(-) delete mode 100644 website/static/network-bg.jpg delete mode 100644 website/static/network-bg.svg diff --git a/website/__pycache__/app.cpython-311.pyc b/website/__pycache__/app.cpython-311.pyc index 1f4242025fa2901ff625a30ea012194771c3fb4c..87c2fdef3a89e7e04b222d0ec4a3c855b145656d 100644 GIT binary patch delta 20 acmaFT&-|pHnR7WWFBbz4ByZ%reH;Kr%Lae| delta 20 acmaFT&-|pHnR7WWFBbz4=x^k_eH;Kq#|BIQ diff --git a/website/static/mindmap.js b/website/static/mindmap.js index 26a6c84..489c923 100644 --- a/website/static/mindmap.js +++ b/website/static/mindmap.js @@ -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 = ` +
+

${thought.title}

+
+ ${new Date(thought.created_at).toLocaleDateString('de-DE')} + ${thought.author ? `von ${thought.author.username}` : ''} +
+
+
+

${thought.abstract || thought.content.substring(0, 150) + '...'}

+
+ + `; + + // 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 = ` +
+ +
+

Keine Gedanken verknüpft

+

Zu "${node.name}" sind noch keine Gedanken verknüpft.

+
+ + Gedanken hinzufügen + +
+ `; + + container.appendChild(emptyState); + } + + // Rendert einen Fehlerzustand + renderErrorState(container) { + if (!container) return; + + const errorState = document.createElement('div'); + errorState.className = 'error-thoughts-state'; + + errorState.innerHTML = ` +
+ +
+

Fehler beim Laden

+

Die Gedanken konnten nicht geladen werden. Bitte versuche es später erneut.

+ + `; + + // 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 diff --git a/website/static/network-bg.jpg b/website/static/network-bg.jpg deleted file mode 100644 index 25e474e..0000000 --- a/website/static/network-bg.jpg +++ /dev/null @@ -1,7 +0,0 @@ -/* Dies ist ein Platzhalter für das Netzwerk-Hintergrundbild mit Base64-Kodierung. - Das eigentliche Bild sollte hier durch eine echte JPG-Datei ersetzt werden. - - Empfohlene Bildgröße: mindestens 1920x1080px - Optimaler Stil: Dunkler Hintergrund mit abstrakten Verbindungslinien und Punkten - - Da wir keine echte JPG-Datei erstellen können, verwende stattdessen eine SVG-Datei mit dem gleichen Namen. */ diff --git a/website/static/network-bg.svg b/website/static/network-bg.svg deleted file mode 100644 index 7ae76ba..0000000 --- a/website/static/network-bg.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/website/templates/login.html b/website/templates/login.html index 73377fe..903afa8 100644 --- a/website/templates/login.html +++ b/website/templates/login.html @@ -3,45 +3,52 @@ {% block title %}Anmelden{% endblock %} {% block content %} -
-
-
-

- - Anmelden -

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

+ + Anmelden +

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

Noch kein Konto? Registrieren

-
- + +
+ +
+ +
+

Noch kein Konto? Registrieren

+
+ +
diff --git a/website/templates/register.html b/website/templates/register.html index 5ecbe0c..0d20104 100644 --- a/website/templates/register.html +++ b/website/templates/register.html @@ -3,56 +3,113 @@ {% block title %}Registrieren{% endblock %} {% block content %} -
-
-
-

- +
+
+
+

+ Registrieren

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

Bereits registriert? Anmelden

+
+

Bereits registriert? Anmelden

+ + {% endblock %} \ No newline at end of file