diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc
index 399441f..621e517 100644
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
diff --git a/app.py b/app.py
index 1b9a45b..a0d7e87 100644
--- a/app.py
+++ b/app.py
@@ -345,23 +345,35 @@ def index():
# Route for the mindmap page
@app.route('/mindmap')
def mindmap():
- """Zeigt die öffentliche Mindmap an."""
- try:
- # Sicherstellen, dass wir Kategorien haben
- if Category.query.count() == 0:
- create_default_categories()
-
- # Hole alle Kategorien der obersten Ebene
- categories = Category.query.filter_by(parent_id=None).all()
-
- # Transformiere Kategorien in ein anzeigbares Format für die Vorlage
- category_tree = [build_category_tree(cat) for cat in categories]
-
- return render_template('mindmap.html', categories=category_tree)
- except Exception as e:
- # Bei Fehler leere Kategorienliste übergeben und Fehler protokollieren
- print(f"Fehler beim Laden der Mindmap-Kategorien: {str(e)}")
- return render_template('mindmap.html', categories=[], error=str(e))
+ """Zeigt die Mindmap-Seite an."""
+
+ # Benutzer-Mindmaps, falls angemeldet
+ user_mindmaps = []
+ if current_user.is_authenticated:
+ user_mindmaps = UserMindmap.query.filter_by(user_id=current_user.id).all()
+
+ # Stelle sicher, dass der "Wissen"-Knoten existiert
+ wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
+ if not wissen_node:
+ wissen_node = MindMapNode(
+ name="Wissen",
+ description="Zentrale Wissensbasis",
+ color_code="#4299E1",
+ is_public=True
+ )
+ db.session.add(wissen_node)
+ db.session.commit()
+ print("'Wissen'-Knoten wurde erstellt")
+
+ # Überprüfe, ob es Kategorien gibt, sonst erstelle sie
+ if Category.query.count() == 0:
+ create_default_categories()
+ print("Kategorien wurden erstellt")
+
+ # Stelle sicher, dass die Route für statische Dateien korrekt ist
+ mindmap_js_path = url_for('static', filename='js/mindmap-init.js')
+
+ return render_template('mindmap.html', user_mindmaps=user_mindmaps, mindmap_js_path=mindmap_js_path)
# Route for user profile
@app.route('/profile')
@@ -1441,14 +1453,42 @@ def refresh_mindmap():
if Category.query.count() == 0:
create_default_categories()
+ # Überprüfe, ob wir bereits einen "Wissen"-Knoten haben
+ wissen_node = MindMapNode.query.filter_by(name="Wissen").first()
+
+ # Wenn kein "Wissen"-Knoten existiert, erstelle ihn
+ if not wissen_node:
+ wissen_node = MindMapNode(
+ name="Wissen",
+ description="Zentrale Wissensbasis",
+ color_code="#4299E1",
+ is_public=True
+ )
+ db.session.add(wissen_node)
+ db.session.commit()
+
# Hole alle Kategorien und Knoten
categories = Category.query.filter_by(parent_id=None).all()
category_tree = [build_category_tree(cat) for cat in categories]
- # Hole alle Mindmap-Knoten
- nodes = MindMapNode.query.all()
- node_data = []
+ # Hole alle Mindmap-Knoten außer dem "Wissen"-Knoten
+ nodes = MindMapNode.query.filter(MindMapNode.id != wissen_node.id).all()
+ # Vorbereiten der Node- und Edge-Arrays für die Antwort
+ node_data = []
+ edge_data = []
+
+ # Zuerst den "Wissen"-Knoten hinzufügen
+ node_data.append({
+ 'id': wissen_node.id,
+ 'name': wissen_node.name,
+ 'description': wissen_node.description or '',
+ 'color_code': wissen_node.color_code or '#4299E1',
+ 'thought_count': len(wissen_node.thoughts),
+ 'category_id': wissen_node.category_id
+ })
+
+ # Dann die anderen Knoten
for node in nodes:
node_obj = {
'id': node.id,
@@ -1459,15 +1499,28 @@ def refresh_mindmap():
'category_id': node.category_id
}
- # Verbindungen hinzufügen
- node_obj['connections'] = [{'target': child.id} for child in node.children]
+ # Verbinde alle Top-Level-Knoten mit dem Wissen-Knoten
+ if not node.parents.all():
+ edge_data.append({
+ 'source': wissen_node.id,
+ 'target': node.id
+ })
+
+ # Verbindungen zwischen vorhandenen Knoten hinzufügen
+ node_children = node.children.all()
+ for child in node_children:
+ edge_data.append({
+ 'source': node.id,
+ 'target': child.id
+ })
node_data.append(node_obj)
return jsonify({
'success': True,
'categories': category_tree,
- 'nodes': node_data
+ 'nodes': node_data,
+ 'edges': edge_data
})
except Exception as e:
@@ -1490,4 +1543,9 @@ def mindmap_page():
@app.route('/community_forum')
def redirect_to_index():
"""Leitet alle Community/Forum-URLs zur Startseite um"""
- return redirect(url_for('index'))
\ No newline at end of file
+ return redirect(url_for('index'))
+
+@app.route('/static/js/mindmap-init.js')
+def serve_mindmap_init_js():
+ """Bedient die Mindmap-Initialisierungsdatei."""
+ return app.send_static_file('js/mindmap-init.js'), 200, {'Content-Type': 'application/javascript'}
\ No newline at end of file
diff --git a/database/systades.db b/database/systades.db
index 6e65762..c9cf76e 100644
Binary files a/database/systades.db and b/database/systades.db differ
diff --git a/static/js/mindmap-init.js b/static/js/mindmap-init.js
new file mode 100644
index 0000000..f0de75f
--- /dev/null
+++ b/static/js/mindmap-init.js
@@ -0,0 +1,420 @@
+/**
+ * Mindmap-Initialisierer
+ * Lädt und initialisiert die Mindmap-Visualisierung
+ */
+
+// Warte bis DOM geladen ist
+document.addEventListener('DOMContentLoaded', function() {
+ // Prüfe, ob wir auf der Mindmap-Seite sind
+ const cyContainer = document.getElementById('cy');
+
+ if (!cyContainer) {
+ console.log('Kein Mindmap-Container gefunden, überspringe Initialisierung.');
+ return;
+ }
+
+ console.log('Initialisiere Mindmap-Visualisierung...');
+
+ // Prüfe, ob Cytoscape.js verfügbar ist
+ if (typeof cytoscape === 'undefined') {
+ loadScript('/static/js/cytoscape.min.js', initMindmap);
+ } else {
+ initMindmap();
+ }
+});
+
+/**
+ * Lädt ein Script dynamisch
+ * @param {string} src - Quelldatei
+ * @param {Function} callback - Callback nach dem Laden
+ */
+function loadScript(src, callback) {
+ const script = document.createElement('script');
+ script.src = src;
+ script.onload = callback;
+ document.head.appendChild(script);
+}
+
+/**
+ * Initialisiert die Mindmap-Visualisierung
+ */
+function initMindmap() {
+ const cyContainer = document.getElementById('cy');
+ const fitBtn = document.getElementById('fit-btn');
+ const resetBtn = document.getElementById('reset-btn');
+ const toggleLabelsBtn = document.getElementById('toggle-labels-btn');
+ const nodeInfoPanel = document.getElementById('node-info-panel');
+ const nodeDescription = document.getElementById('node-description');
+ const connectedNodes = document.getElementById('connected-nodes');
+
+ let labelsVisible = true;
+ let selectedNode = null;
+
+ // Erstelle Cytoscape-Instanz
+ const cy = cytoscape({
+ container: cyContainer,
+ style: getDefaultStyles(),
+ layout: {
+ name: 'cose',
+ animate: true,
+ animationDuration: 800,
+ nodeDimensionsIncludeLabels: true,
+ padding: 50,
+ spacingFactor: 1.2,
+ randomize: true,
+ componentSpacing: 100,
+ nodeRepulsion: 8000,
+ edgeElasticity: 100,
+ nestingFactor: 1.2,
+ gravity: 80
+ },
+ wheelSensitivity: 0.3,
+ });
+
+ // Daten vom Server laden
+ loadMindmapData(cy);
+
+ // Event-Handler zuweisen
+ setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes);
+}
+
+/**
+ * Lädt die Mindmap-Daten vom Server
+ * @param {Object} cy - Cytoscape-Instanz
+ */
+function loadMindmapData(cy) {
+ fetch('/api/mindmap')
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`HTTP Fehler: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then(data => {
+ if (!data.nodes || data.nodes.length === 0) {
+ console.log('Keine Daten gefunden, versuche Refresh-API...');
+ return fetch('/api/refresh-mindmap')
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`HTTP Fehler beim Refresh: ${response.status}`);
+ }
+ return response.json();
+ });
+ }
+ return data;
+ })
+ .then(data => {
+ console.log('Mindmap-Daten geladen:', data);
+
+ // Cytoscape-Elemente vorbereiten
+ const elements = [];
+
+ // Prüfen, ob "Wissen"-Knoten existiert
+ let rootNode = data.nodes.find(node => node.name === "Wissen");
+
+ // Wenn nicht, Root-Knoten hinzufügen
+ if (!rootNode) {
+ rootNode = {
+ id: 'root',
+ name: 'Wissen',
+ description: 'Zentrale Wissensbasis',
+ color_code: '#4299E1'
+ };
+ data.nodes.unshift(rootNode);
+ }
+
+ // Knoten hinzufügen
+ data.nodes.forEach(node => {
+ elements.push({
+ group: 'nodes',
+ data: {
+ id: node.id.toString(),
+ name: node.name,
+ description: node.description || '',
+ color: node.color_code || '#8B5CF6',
+ isRoot: node.name === 'Wissen'
+ }
+ });
+ });
+
+ // Kanten hinzufügen, wenn vorhanden
+ if (data.edges && data.edges.length > 0) {
+ data.edges.forEach(edge => {
+ elements.push({
+ group: 'edges',
+ data: {
+ id: `${edge.source}-${edge.target}`,
+ source: edge.source.toString(),
+ target: edge.target.toString()
+ }
+ });
+ });
+ } else {
+ // Wenn keine Kanten definiert sind, verbinde alle Knoten mit dem Root-Knoten
+ const rootId = rootNode.id.toString();
+
+ data.nodes.forEach(node => {
+ if (node.id.toString() !== rootId) {
+ elements.push({
+ group: 'edges',
+ data: {
+ id: `${rootId}-${node.id}`,
+ source: rootId,
+ target: node.id.toString()
+ }
+ });
+ }
+ });
+ }
+
+ // Elemente zu Cytoscape hinzufügen
+ cy.elements().remove();
+ cy.add(elements);
+
+ // Layout anwenden
+ cy.layout({
+ name: 'cose',
+ animate: true,
+ animationDuration: 800,
+ nodeDimensionsIncludeLabels: true,
+ padding: 50,
+ spacingFactor: 1.5,
+ randomize: false,
+ fit: true
+ }).run();
+
+ // Nach dem Laden Event auslösen
+ document.dispatchEvent(new CustomEvent('mindmap-loaded'));
+ })
+ .catch(error => {
+ console.error('Fehler beim Laden der Mindmap-Daten:', error);
+
+ // Fallback mit Standard-Daten
+ const fallbackData = {
+ nodes: [
+ { id: 1, name: 'Wissen', description: 'Zentrale Wissensbasis', color_code: '#4299E1' },
+ { id: 2, name: 'Philosophie', description: 'Philosophisches Denken', color_code: '#9F7AEA' },
+ { id: 3, name: 'Wissenschaft', description: 'Wissenschaftliche Erkenntnisse', color_code: '#48BB78' },
+ { id: 4, name: 'Technologie', description: 'Technologische Entwicklungen', color_code: '#ED8936' },
+ { id: 5, name: 'Künste', description: 'Künstlerische Ausdrucksformen', color_code: '#ED64A6' }
+ ],
+ edges: [
+ { source: 1, target: 2 },
+ { source: 1, target: 3 },
+ { source: 1, target: 4 },
+ { source: 1, target: 5 }
+ ]
+ };
+
+ const fallbackElements = [];
+
+ // Knoten hinzufügen
+ fallbackData.nodes.forEach(node => {
+ fallbackElements.push({
+ group: 'nodes',
+ data: {
+ id: node.id.toString(),
+ name: node.name,
+ description: node.description || '',
+ color: node.color_code || '#8B5CF6',
+ isRoot: node.name === 'Wissen'
+ }
+ });
+ });
+
+ // Kanten hinzufügen
+ fallbackData.edges.forEach(edge => {
+ fallbackElements.push({
+ group: 'edges',
+ data: {
+ id: `${edge.source}-${edge.target}`,
+ source: edge.source.toString(),
+ target: edge.target.toString()
+ }
+ });
+ });
+
+ // Elemente zu Cytoscape hinzufügen
+ cy.elements().remove();
+ cy.add(fallbackElements);
+
+ // Layout anwenden
+ cy.layout({ name: 'cose', animate: true }).run();
+ });
+}
+
+/**
+ * Richtet Event-Listener für die Mindmap ein
+ * @param {Object} cy - Cytoscape-Instanz
+ * @param {HTMLElement} fitBtn - Fit-Button
+ * @param {HTMLElement} resetBtn - Reset-Button
+ * @param {HTMLElement} toggleLabelsBtn - Toggle-Labels-Button
+ * @param {HTMLElement} nodeInfoPanel - Node-Info-Panel
+ * @param {HTMLElement} nodeDescription - Node-Description
+ * @param {HTMLElement} connectedNodes - Connected-Nodes-Container
+ */
+function setupEventListeners(cy, fitBtn, resetBtn, toggleLabelsBtn, nodeInfoPanel, nodeDescription, connectedNodes) {
+ let labelsVisible = true;
+
+ // Fit-Button
+ if (fitBtn) {
+ fitBtn.addEventListener('click', function() {
+ cy.fit();
+ });
+ }
+
+ // Reset-Button
+ if (resetBtn) {
+ resetBtn.addEventListener('click', function() {
+ cy.layout({ name: 'cose', animate: true }).run();
+ });
+ }
+
+ // Toggle-Labels-Button
+ if (toggleLabelsBtn) {
+ toggleLabelsBtn.addEventListener('click', function() {
+ labelsVisible = !labelsVisible;
+ cy.style()
+ .selector('node')
+ .style({
+ 'text-opacity': labelsVisible ? 1 : 0
+ })
+ .update();
+ });
+ }
+
+ // Knoten-Klick
+ cy.on('tap', 'node', function(evt) {
+ const node = evt.target;
+
+ // Zuvor ausgewählten Knoten zurücksetzen
+ cy.nodes().removeClass('selected');
+
+ // Neuen Knoten auswählen
+ node.addClass('selected');
+
+ if (nodeInfoPanel && nodeDescription && connectedNodes) {
+ // Info-Panel aktualisieren
+ nodeDescription.textContent = node.data('description') || 'Keine Beschreibung verfügbar.';
+
+ // Verbundene Knoten anzeigen
+ connectedNodes.innerHTML = '';
+
+ // Verbundene Knoten sammeln
+ const connectedNodesList = node.neighborhood('node');
+
+ if (connectedNodesList.length > 0) {
+ connectedNodesList.forEach(connectedNode => {
+ // Nicht den ausgewählten Knoten selbst anzeigen
+ if (connectedNode.id() !== node.id()) {
+ const nodeLink = document.createElement('span');
+ nodeLink.className = 'node-link';
+ nodeLink.textContent = connectedNode.data('name');
+ nodeLink.style.backgroundColor = connectedNode.data('color');
+
+ // Klick-Ereignis, um zu diesem Knoten zu wechseln
+ nodeLink.addEventListener('click', function() {
+ connectedNode.select();
+ cy.animate({
+ center: { eles: connectedNode },
+ duration: 500,
+ easing: 'ease-in-out-cubic'
+ });
+ });
+
+ connectedNodes.appendChild(nodeLink);
+ }
+ });
+ } else {
+ connectedNodes.innerHTML = 'Keine verbundenen Knoten';
+ }
+
+ // Panel anzeigen
+ nodeInfoPanel.classList.add('visible');
+ }
+ });
+
+ // Hintergrund-Klick
+ cy.on('tap', function(evt) {
+ if (evt.target === cy) {
+ // Klick auf den Hintergrund
+ cy.nodes().removeClass('selected');
+
+ // Info-Panel verstecken
+ if (nodeInfoPanel) {
+ nodeInfoPanel.classList.remove('visible');
+ }
+ }
+ });
+
+ // Dark Mode-Änderungen
+ document.addEventListener('darkModeToggled', function(event) {
+ const isDark = event.detail.isDark;
+ cy.style(getDefaultStyles(isDark));
+ });
+}
+
+/**
+ * Liefert die Standard-Stile für die Mindmap
+ * @param {boolean} darkMode - Ob der Dark Mode aktiv ist
+ * @returns {Array} Array von Cytoscape-Stilen
+ */
+function getDefaultStyles(darkMode = document.documentElement.classList.contains('dark')) {
+ return [
+ {
+ selector: 'node',
+ style: {
+ 'background-color': 'data(color)',
+ 'label': 'data(name)',
+ 'width': 40,
+ 'height': 40,
+ 'font-size': 12,
+ 'text-valign': 'bottom',
+ 'text-halign': 'center',
+ 'text-margin-y': 8,
+ 'color': darkMode ? '#f1f5f9' : '#334155',
+ 'text-background-color': darkMode ? 'rgba(30, 41, 59, 0.8)' : 'rgba(241, 245, 249, 0.8)',
+ 'text-background-opacity': 0.8,
+ 'text-background-padding': '2px',
+ 'text-background-shape': 'roundrectangle',
+ 'text-wrap': 'ellipsis',
+ 'text-max-width': '100px'
+ }
+ },
+ {
+ selector: 'node[?isRoot]',
+ style: {
+ 'width': 60,
+ 'height': 60,
+ 'font-size': 14,
+ 'font-weight': 'bold',
+ 'text-background-opacity': 0.9,
+ 'text-background-color': '#4299E1'
+ }
+ },
+ {
+ selector: 'edge',
+ style: {
+ 'width': 2,
+ 'line-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
+ 'target-arrow-color': darkMode ? 'rgba(255, 255, 255, 0.15)' : 'rgba(30, 41, 59, 0.15)',
+ 'curve-style': 'bezier',
+ 'target-arrow-shape': 'triangle'
+ }
+ },
+ {
+ selector: 'node.selected',
+ style: {
+ 'background-color': 'data(color)',
+ 'border-width': 3,
+ 'border-color': '#8b5cf6',
+ 'width': 50,
+ 'height': 50,
+ 'font-size': 14,
+ 'font-weight': 'bold',
+ 'text-background-color': '#8b5cf6',
+ 'text-background-opacity': 0.9
+ }
+ }
+ ];
+}
\ No newline at end of file
diff --git a/static/js/modules/mindmap.js b/static/js/modules/mindmap.js
index 34148fb..7ae6be2 100644
Binary files a/static/js/modules/mindmap.js and b/static/js/modules/mindmap.js differ
diff --git a/templates/mindmap.html b/templates/mindmap.html
index 0e7a940..9ea0ff9 100644
--- a/templates/mindmap.html
+++ b/templates/mindmap.html
@@ -110,6 +110,95 @@
background-color: rgba(0, 0, 0, 0.05);
}
+ /* Info-Panel */
+ .mindmap-info-panel {
+ position: absolute;
+ right: 1rem;
+ top: 5rem;
+ width: 280px;
+ background-color: rgba(15, 23, 42, 0.85);
+ border-radius: 8px;
+ box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
+ padding: 1rem;
+ color: #f1f5f9;
+ opacity: 0;
+ transform: translateX(20px);
+ transition: all 0.3s ease;
+ pointer-events: none;
+ z-index: 10;
+ backdrop-filter: blur(4px);
+ }
+
+ .mindmap-info-panel.visible {
+ opacity: 1;
+ transform: translateX(0);
+ pointer-events: auto;
+ }
+
+ .info-panel-title {
+ font-size: 1.1rem;
+ font-weight: 600;
+ margin-bottom: 0.75rem;
+ padding-bottom: 0.5rem;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ }
+
+ .info-panel-description {
+ font-size: 0.875rem;
+ margin-bottom: 1rem;
+ line-height: 1.5;
+ }
+
+ .node-navigation-title {
+ font-size: 0.9rem;
+ font-weight: 500;
+ margin-bottom: 0.5rem;
+ }
+
+ .node-links {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ }
+
+ .node-link {
+ display: inline-block;
+ padding: 0.25rem 0.5rem;
+ font-size: 0.75rem;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: transform 0.2s ease;
+ color: white;
+ }
+
+ .node-link:hover {
+ transform: translateY(-2px);
+ }
+
+ /* Mindmap-Toolbar-Buttons */
+ .mindmap-action-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ padding: 0.35rem 0.7rem;
+ font-size: 0.8rem;
+ border-radius: 0.25rem;
+ background-color: rgba(124, 58, 237, 0.1);
+ color: #8b5cf6;
+ border: 1px solid rgba(124, 58, 237, 0.2);
+ cursor: pointer;
+ transition: all 0.2s ease;
+ }
+
+ .dark .mindmap-action-btn {
+ background-color: rgba(124, 58, 237, 0.15);
+ border-color: rgba(124, 58, 237, 0.3);
+ }
+
+ .mindmap-action-btn:hover {
+ background-color: rgba(124, 58, 237, 0.2);
+ }
+
/* Zusätzliches Layout */
.mindmap-section {
display: grid;
@@ -252,14 +341,17 @@
{% endblock %}
{% block extra_js %}
+
+
+
+
+
+
{% endblock %}
\ No newline at end of file