Compare commits

...

22 Commits

Author SHA1 Message Date
d3405a7031 chore: Aktualisierung der Mindmap-Funktionalität und Integration von CSS/JS-Templates 2025-05-14 13:58:51 +02:00
5793902e47 chore: Änderungen commited 2025-05-14 13:56:11 +02:00
e73ccd7e80 "Update mindmap template with Convention-Format: templates/templates/mindmap.html" 2025-05-14 13:53:57 +02:00
e6784b712d 🎉 feat: "Add Minducture mindmap CSS and JS template integration" 2025-05-14 13:51:16 +02:00
35b5f321d4 chore: Änderungen commited 2025-05-14 13:48:24 +02:00
b68f65cc76 "Update mindmap Mindmap functionality 2025-05-14 13:47:35 +02:00
3a2f721f63 chore: Änderungen commited 2025-05-14 13:44:48 +02:00
5933195196 "Refactor Mindate mindmap UI updates for mindmap controls and components" (feat) 2025-05-14 13:41:24 +02:00
beccfa25a6 chore: Änderungen commited 2025-05-14 13:37:29 +02:00
bc5cef3ba8 "feat: Implement mindmap control enhancements and controls updates" 2025-05-14 12:51:53 +02:00
b867af9c8b chore: Änderungen commited 2025-05-14 12:47:02 +02:00
ee04432a49 chore: Änderungen commited 2025-05-14 12:43:13 +02:00
bbcee7f610 chore: Änderungen commited 2025-05-14 12:39:23 +02:00
1eb47fc230 chore: Änderungen commited 2025-05-14 12:15:57 +02:00
2921c5a824 chore: Änderungen commited 2025-05-14 12:13:19 +02:00
c98e238841 chore: Änderungen commited 2025-05-14 12:07:57 +02:00
af30a208ca chore: Änderungen commited 2025-05-14 12:02:15 +02:00
2e2f35ccc1 chore: Änderungen commited 2025-05-14 11:58:31 +02:00
fd293e53e1 chore: Änderungen commited 2025-05-14 11:55:58 +02:00
2b19cb000b chore: Änderungen commited 2025-05-14 11:39:58 +02:00
3aefe6c5e6 chore: Änderungen commited 2025-05-14 11:23:33 +02:00
c7b87dc643 chore: Änderungen commited 2025-05-14 11:21:10 +02:00
15 changed files with 1295 additions and 6545 deletions

Binary file not shown.

Binary file not shown.

495
app.py
View File

@@ -2062,6 +2062,320 @@ def dummy_network_bg():
"""Leere Antwort für die nicht mehr verwendeten Netzwerk-Hintergrundbilder.""" """Leere Antwort für die nicht mehr verwendeten Netzwerk-Hintergrundbilder."""
return '', 200 return '', 200
def get_category_mindmap_data(category_name):
"""Generische Funktion zum Abrufen der Mindmap-Daten für eine Kategorie."""
try:
# Kategorie mit allen Unterkategorien in einer Abfrage laden
category = Category.query.filter_by(name=category_name).options(
joinedload(Category.children)
).first_or_404()
# Basis-Knoten erstellen
nodes = [{
'id': f'cat_{category.id}',
'name': category.name,
'description': category.description or '',
'color_code': category.color_code or '#9F7AEA',
'is_center': True,
'has_children': bool(category.children),
'icon': category.icon or 'fa-solid fa-circle'
}]
# Unterkategorien hinzufügen
for subcat in category.children:
nodes.append({
'id': f'cat_{subcat.id}',
'name': subcat.name,
'description': subcat.description or '',
'color_code': subcat.color_code or '#9F7AEA',
'category': category_name,
'has_children': bool(subcat.children),
'icon': subcat.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': f'cat_{category.id}',
'target': f'cat_{subcat.id}',
'strength': 0.8
} for subcat in category.children]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der {category_name}-Mindmap: {str(e)}")
return jsonify({
'success': False,
'error': f'{category_name}-Mindmap konnte nicht geladen werden',
'details': str(e)
}), 500
# API-Endpunkt für die Root-Mindmap
@app.route('/api/mindmap/root')
def get_root_mindmap_data():
"""Liefert die Daten für die Root-Mindmap."""
try:
# Hauptkategorien mit Unterkategorien in einer Abfrage laden
categories = Category.query.filter_by(parent_id=None).options(
joinedload(Category.children)
).all()
# Überprüfen, ob Kategorien vorhanden sind
if not categories:
print("Keine Hauptkategorien gefunden")
return jsonify({
'success': False,
'error': 'Keine Hauptkategorien gefunden',
'details': 'Bitte führen Sie das Datenbank-Initialisierungsskript aus'
}), 404
print(f"Gefundene Hauptkategorien: {[cat.name for cat in categories]}")
# Basis-Knoten erstellen
nodes = [{
'id': 'root',
'name': 'Wissen',
'description': 'Zentrale Wissensbasis',
'color_code': '#4299E1',
'is_center': True,
'has_children': bool(categories),
'icon': 'fa-solid fa-circle'
}]
# Kategorien als Knoten hinzufügen
for category in categories:
nodes.append({
'id': f'cat_{category.id}',
'name': category.name,
'description': category.description or '',
'color_code': category.color_code or '#9F7AEA',
'category': category.name,
'has_children': bool(len(category.children) > 0),
'icon': category.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': 'root',
'target': f'cat_{category.id}',
'strength': 0.8
} for category in categories]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der Root-Mindmap: {str(e)}")
traceback.print_exc()
return jsonify({
'success': False,
'error': 'Root-Mindmap konnte nicht geladen werden',
'details': str(e)
}), 500
# Spezifische Routen für Kategorien
@app.route('/api/mindmap/philosophy')
def get_philosophy_mindmap_data():
return get_category_mindmap_data('Philosophie')
@app.route('/api/mindmap/science')
def get_science_mindmap_data():
return get_category_mindmap_data('Wissenschaft')
@app.route('/api/mindmap/technology')
def get_technology_mindmap_data():
return get_category_mindmap_data('Technologie')
@app.route('/api/mindmap/arts')
def get_arts_mindmap_data():
return get_category_mindmap_data('Künste')
# Generische Route für spezifische Knoten
@app.route('/api/mindmap/<node_id>')
def get_mindmap_data(node_id):
"""Liefert die Daten für einen spezifischen Mindmap-Knoten."""
try:
# Prüfen, ob es sich um eine spezielle Route handelt
if node_id in ['root', 'philosophy', 'science', 'technology', 'arts']:
return jsonify({
'success': False,
'error': 'Ungültige Knoten-ID',
'details': 'Diese ID ist für spezielle Routen reserviert'
}), 400
# Knoten mit Unterknoten in einer Abfrage laden
node = MindMapNode.query.options(
joinedload(MindMapNode.children)
).get_or_404(node_id)
# Basis-Knoten erstellen
nodes = [{
'id': str(node.id),
'name': node.name,
'description': node.description or '',
'color_code': node.color_code or '#9F7AEA',
'is_center': True,
'has_children': bool(node.children),
'icon': node.icon or 'fa-solid fa-circle'
}]
# Unterknoten hinzufügen
for child in node.children:
nodes.append({
'id': str(child.id),
'name': child.name,
'description': child.description or '',
'color_code': child.color_code or '#9F7AEA',
'category': node.name,
'has_children': bool(child.children),
'icon': child.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': str(node.id),
'target': str(child.id),
'strength': 0.8
} for child in node.children]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der Mindmap-Daten für Knoten {node_id}: {str(e)}")
return jsonify({
'success': False,
'error': 'Mindmap-Daten konnten nicht geladen werden',
'details': str(e)
}), 500
# API-Endpunkt zum Speichern von Mindmap-Änderungen
@app.route('/api/mindmap/save', methods=['POST'])
@login_required
def save_mindmap_changes():
"""Speichert Änderungen an der Mindmap."""
try:
data = request.get_json()
if not data:
return jsonify({
'success': False,
'error': 'Keine Daten erhalten',
'details': 'Der Request enthält keine JSON-Daten'
}), 400
# Überprüfen, ob die erforderlichen Daten vorhanden sind
if 'nodes' not in data or 'edges' not in data:
return jsonify({
'success': False,
'error': 'Unvollständige Daten',
'details': 'Nodes oder Edges fehlen'
}), 400
# Verarbeiten der Knoten
for node_data in data['nodes']:
# ID überprüfen: neue Knoten haben temporäre IDs, die mit 'new-' beginnen
node_id = node_data.get('id')
if isinstance(node_id, str) and node_id.startswith('new-'):
# Neuen Knoten erstellen
new_node = MindMapNode(
name=node_data.get('name', 'Neuer Knoten'),
description=node_data.get('description', ''),
color_code=node_data.get('color_code', '#9F7AEA'),
icon=node_data.get('icon', 'fa-solid fa-circle'),
is_public=True,
created_by_id=current_user.id
)
# Kategorie zuordnen, falls vorhanden
category_name = node_data.get('category')
if category_name:
category = Category.query.filter_by(name=category_name).first()
if category:
new_node.category_id = category.id
db.session.add(new_node)
# Wir müssen flushen, um eine ID zu erhalten (für die Kanten-Erstellung)
db.session.flush()
# Temporäre ID für die Mapping-Tabelle speichern
node_data['real_id'] = new_node.id
else:
# Bestehenden Knoten aktualisieren
node = MindMapNode.query.get(int(node_id))
if node:
node.name = node_data.get('name', node.name)
node.description = node_data.get('description', node.description)
node.color_code = node_data.get('color_code', node.color_code)
node.icon = node_data.get('icon', node.icon)
# Kategorie aktualisieren, falls vorhanden
category_name = node_data.get('category')
if category_name:
category = Category.query.filter_by(name=category_name).first()
if category:
node.category_id = category.id
# Position speichern, falls vorhanden
if 'position_x' in node_data and 'position_y' in node_data:
# Hier könnte die Position gespeichert werden, wenn das Modell erweitert wird
pass
node_data['real_id'] = node.id
# Bestehende Kanten für die betroffenen Knoten löschen
# (wir ersetzen alle Kanten durch die neuen)
node_ids = [int(node_data.get('real_id')) for node_data in data['nodes'] if 'real_id' in node_data]
# Neue Kanten erstellen (mit den richtigen IDs aus der Mapping-Tabelle)
for edge_data in data['edges']:
source_id = edge_data.get('source')
target_id = edge_data.get('target')
# Temporäre IDs durch reale IDs ersetzen
for node_data in data['nodes']:
if node_data.get('id') == source_id and 'real_id' in node_data:
source_id = node_data['real_id']
if node_data.get('id') == target_id and 'real_id' in node_data:
target_id = node_data['real_id']
# Beziehung zwischen Knoten erstellen/aktualisieren
source_node = MindMapNode.query.get(int(source_id))
target_node = MindMapNode.query.get(int(target_id))
if source_node and target_node:
# Prüfen, ob die Beziehung bereits existiert
if target_node not in source_node.children.all():
source_node.children.append(target_node)
db.session.commit()
return jsonify({
'success': True,
'message': 'Mindmap erfolgreich gespeichert',
'node_mapping': {node_data.get('id'): node_data.get('real_id')
for node_data in data['nodes']
if 'real_id' in node_data and node_data.get('id') != node_data.get('real_id')}
})
except Exception as e:
db.session.rollback()
print(f"Fehler beim Speichern der Mindmap: {str(e)}")
traceback.print_exc()
return jsonify({
'success': False,
'error': 'Mindmap konnte nicht gespeichert werden',
'details': str(e)
}), 500
# Route zum expliziten Neu-Laden der Umgebungsvariablen # Route zum expliziten Neu-Laden der Umgebungsvariablen
@app.route('/admin/reload-env', methods=['POST']) @app.route('/admin/reload-env', methods=['POST'])
@admin_required @admin_required
@@ -2343,184 +2657,3 @@ if __name__ == '__main__':
db.create_all() db.create_all()
socketio.run(app, debug=True, host='0.0.0.0') socketio.run(app, debug=True, host='0.0.0.0')
def get_category_mindmap_data(category_name):
"""Generische Funktion zum Abrufen der Mindmap-Daten für eine Kategorie."""
try:
# Kategorie mit allen Unterkategorien in einer Abfrage laden
category = Category.query.filter_by(name=category_name).options(
joinedload(Category.children)
).first_or_404()
# Basis-Knoten erstellen
nodes = [{
'id': f'cat_{category.id}',
'name': category.name,
'description': category.description or '',
'color_code': category.color_code or '#9F7AEA',
'is_center': True,
'has_children': bool(category.children),
'icon': category.icon or 'fa-solid fa-circle'
}]
# Unterkategorien hinzufügen
for subcat in category.children:
nodes.append({
'id': f'cat_{subcat.id}',
'name': subcat.name,
'description': subcat.description or '',
'color_code': subcat.color_code or '#9F7AEA',
'category': category_name,
'has_children': bool(subcat.children),
'icon': subcat.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': f'cat_{category.id}',
'target': f'cat_{subcat.id}',
'strength': 0.8
} for subcat in category.children]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der {category_name}-Mindmap: {str(e)}")
return jsonify({
'success': False,
'error': f'{category_name}-Mindmap konnte nicht geladen werden',
'details': str(e)
}), 500
@app.route('/api/mindmap/root')
def get_root_mindmap_data():
"""Liefert die Daten für die Root-Mindmap."""
try:
# Hauptkategorien mit Unterkategorien in einer Abfrage laden
categories = Category.query.filter_by(parent_id=None).options(
joinedload(Category.children)
).all()
# Basis-Knoten erstellen
nodes = [{
'id': 'root',
'name': 'Wissen',
'description': 'Zentrale Wissensbasis',
'color_code': '#4299E1',
'is_center': True,
'has_children': bool(categories),
'icon': 'fa-solid fa-circle'
}]
# Kategorien als Knoten hinzufügen
for category in categories:
nodes.append({
'id': f'cat_{category.id}',
'name': category.name,
'description': category.description or '',
'color_code': category.color_code or '#9F7AEA',
'category': category.name,
'has_children': bool(category.children),
'icon': category.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': 'root',
'target': f'cat_{category.id}',
'strength': 0.8
} for category in categories]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der Root-Mindmap: {str(e)}")
return jsonify({
'success': False,
'error': 'Root-Mindmap konnte nicht geladen werden',
'details': str(e)
}), 500
# Spezifische Routen für Kategorien
@app.route('/api/mindmap/philosophy')
def get_philosophy_mindmap_data():
return get_category_mindmap_data('Philosophie')
@app.route('/api/mindmap/science')
def get_science_mindmap_data():
return get_category_mindmap_data('Wissenschaft')
@app.route('/api/mindmap/technology')
def get_technology_mindmap_data():
return get_category_mindmap_data('Technologie')
@app.route('/api/mindmap/arts')
def get_arts_mindmap_data():
return get_category_mindmap_data('Künste')
# Generische Route für spezifische Knoten
@app.route('/api/mindmap/<node_id>')
def get_mindmap_data(node_id):
"""Liefert die Daten für einen spezifischen Mindmap-Knoten."""
try:
# Prüfen, ob es sich um eine spezielle Route handelt
if node_id in ['root', 'philosophy', 'science', 'technology', 'arts']:
return jsonify({
'success': False,
'error': 'Ungültige Knoten-ID',
'details': 'Diese ID ist für spezielle Routen reserviert'
}), 400
# Knoten mit Unterknoten in einer Abfrage laden
node = MindMapNode.query.options(
joinedload(MindMapNode.children)
).get_or_404(node_id)
# Basis-Knoten erstellen
nodes = [{
'id': str(node.id),
'name': node.name,
'description': node.description or '',
'color_code': node.color_code or '#9F7AEA',
'is_center': True,
'has_children': bool(node.children),
'icon': node.icon or 'fa-solid fa-circle'
}]
# Unterknoten hinzufügen
for child in node.children:
nodes.append({
'id': str(child.id),
'name': child.name,
'description': child.description or '',
'color_code': child.color_code or '#9F7AEA',
'category': node.name,
'has_children': bool(child.children),
'icon': child.icon or 'fa-solid fa-circle'
})
# Kanten erstellen (vereinheitlichte Schlüssel)
edges = [{
'source': str(node.id),
'target': str(child.id),
'strength': 0.8
} for child in node.children]
return jsonify({
'success': True,
'nodes': nodes,
'edges': edges
})
except Exception as e:
print(f"Fehler beim Abrufen der Mindmap-Daten für Knoten {node_id}: {str(e)}")
return jsonify({
'success': False,
'error': 'Mindmap-Daten konnten nicht geladen werden',
'details': str(e)
}), 500

Binary file not shown.

View File

@@ -1,19 +1,29 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from app import app, initialize_database, db_path
from models import db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating, RelationType
from models import Category, UserMindmap, UserMindmapNode, MindmapNote
import os import os
import sqlite3 import sqlite3
from flask import Flask from flask import Flask
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from datetime import datetime from datetime import datetime
# Pfad zur Datenbank
basedir = os.path.abspath(os.path.dirname(__file__))
db_path = os.path.join(basedir, 'database', 'systades.db')
# Stelle sicher, dass das Verzeichnis existiert
db_dir = os.path.dirname(db_path)
os.makedirs(db_dir, exist_ok=True)
# Erstelle eine temporäre Flask-App, um die Datenbank zu initialisieren # Erstelle eine temporäre Flask-App, um die Datenbank zu initialisieren
app = Flask(__name__) app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database/systades.db' app.config['SQLALCHEMY_DATABASE_URI'] = f'sqlite:///{db_path}'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Importiere die Modelle nach der App-Initialisierung
from models import db, User, Thought, Comment, MindMapNode, ThoughtRelation, ThoughtRating, RelationType
from models import Category, UserMindmap, UserMindmapNode, MindmapNote
db.init_app(app) db.init_app(app)
def init_db(): def init_db():
@@ -69,45 +79,111 @@ def create_default_users():
def create_default_categories(): def create_default_categories():
"""Erstellt die Standardkategorien für die Mindmap""" """Erstellt die Standardkategorien für die Mindmap"""
categories = [ # Hauptkategorien
main_categories = [
{ {
'name': 'Konzept', "name": "Philosophie",
'description': 'Abstrakte Ideen und theoretische Konzepte', "description": "Philosophisches Denken und Konzepte",
'color_code': '#6366f1', "color_code": "#9F7AEA",
'icon': 'lightbulb' "icon": "fa-brain"
}, },
{ {
'name': 'Technologie', "name": "Wissenschaft",
'description': 'Hardware, Software, Tools und Plattformen', "description": "Wissenschaftliche Disziplinen und Erkenntnisse",
'color_code': '#10b981', "color_code": "#60A5FA",
'icon': 'cpu' "icon": "fa-flask"
}, },
{ {
'name': 'Prozess', "name": "Technologie",
'description': 'Workflows, Methodologien und Vorgehensweisen', "description": "Technologische Entwicklungen und Anwendungen",
'color_code': '#f59e0b', "color_code": "#10B981",
'icon': 'git-branch' "icon": "fa-microchip"
}, },
{ {
'name': 'Person', "name": "Künste",
'description': 'Personen, Teams und Organisationen', "description": "Künstlerische Ausdrucksformen und Werke",
'color_code': '#ec4899', "color_code": "#F59E0B",
'icon': 'user' "icon": "fa-palette"
}, },
{ {
'name': 'Dokument', "name": "Psychologie",
'description': 'Dokumentationen, Referenzen und Ressourcen', "description": "Mentale Prozesse und Verhaltensweisen",
'color_code': '#3b82f6', "color_code": "#EF4444",
'icon': 'file-text' "icon": "fa-brain"
} }
] ]
for cat_data in categories: # Hauptkategorien erstellen
category_map = {}
for cat_data in main_categories:
category = Category(**cat_data) category = Category(**cat_data)
db.session.add(category) db.session.add(category)
db.session.flush() # ID generieren
category_map[cat_data["name"]] = category
# Unterkategorien für Philosophie
philosophy_subcategories = [
{"name": "Ethik", "description": "Moralische Grundsätze", "icon": "fa-balance-scale", "color_code": "#8B5CF6"},
{"name": "Logik", "description": "Gesetze des Denkens", "icon": "fa-project-diagram", "color_code": "#8B5CF6"},
{"name": "Erkenntnistheorie", "description": "Natur des Wissens", "icon": "fa-lightbulb", "color_code": "#8B5CF6"}
]
# Unterkategorien für Wissenschaft
science_subcategories = [
{"name": "Physik", "description": "Studie der Materie und Energie", "icon": "fa-atom", "color_code": "#3B82F6"},
{"name": "Biologie", "description": "Studie des Lebens", "icon": "fa-dna", "color_code": "#3B82F6"},
{"name": "Mathematik", "description": "Studie der Zahlen und Strukturen", "icon": "fa-square-root-alt", "color_code": "#3B82F6"}
]
# Unterkategorien für Technologie
tech_subcategories = [
{"name": "Software", "description": "Computerprogramme und Anwendungen", "icon": "fa-code", "color_code": "#059669"},
{"name": "Hardware", "description": "Physische Komponenten der Technik", "icon": "fa-microchip", "color_code": "#059669"},
{"name": "Internet", "description": "Globales Netzwerk und Web", "icon": "fa-globe", "color_code": "#059669"}
]
# Unterkategorien für Künste
arts_subcategories = [
{"name": "Musik", "description": "Klangkunst", "icon": "fa-music", "color_code": "#D97706"},
{"name": "Literatur", "description": "Geschriebene Kunst", "icon": "fa-book", "color_code": "#D97706"},
{"name": "Bildende Kunst", "description": "Visuelle Kunst", "icon": "fa-paint-brush", "color_code": "#D97706"}
]
# Unterkategorien für Psychologie
psychology_subcategories = [
{"name": "Kognition", "description": "Gedächtnisprozesse und Denken", "icon": "fa-brain", "color_code": "#DC2626"},
{"name": "Emotionen", "description": "Gefühle und emotionale Prozesse", "icon": "fa-heart", "color_code": "#DC2626"},
{"name": "Verhalten", "description": "Beobachtbares Verhalten und Reaktionen", "icon": "fa-user", "color_code": "#DC2626"}
]
# Alle Unterkategorien zu ihren Hauptkategorien hinzufügen
for subcat_data in philosophy_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Philosophie"].id
db.session.add(subcat)
for subcat_data in science_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Wissenschaft"].id
db.session.add(subcat)
for subcat_data in tech_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Technologie"].id
db.session.add(subcat)
for subcat_data in arts_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Künste"].id
db.session.add(subcat)
for subcat_data in psychology_subcategories:
subcat = Category(**subcat_data)
subcat.parent_id = category_map["Psychologie"].id
db.session.add(subcat)
db.session.commit() db.session.commit()
print(f"{len(categories)} Kategorien wurden erstellt.") print(f"{len(main_categories)} Hauptkategorien und {len(philosophy_subcategories + science_subcategories + tech_subcategories + arts_subcategories + psychology_subcategories)} Unterkategorien wurden erstellt.")
def create_sample_mindmap(): def create_sample_mindmap():
"""Erstellt eine Beispiel-Mindmap mit Knoten und Beziehungen""" """Erstellt eine Beispiel-Mindmap mit Knoten und Beziehungen"""

File diff suppressed because it is too large Load Diff

View File

@@ -136,11 +136,7 @@ async function loadMindmapData(nodeId = null) {
let errorData; let errorData;
try { try {
errorData = await response.json(); errorData = await response.json();
console.log('API-Fehler Details:', { console.log('API-Fehler Details:', errorData);
status: response.status,
statusText: response.statusText,
errorData
});
} catch (e) { } catch (e) {
console.error('Fehler beim Parsen der Fehlerantwort:', e); console.error('Fehler beim Parsen der Fehlerantwort:', e);
errorData = { errorData = {
@@ -149,25 +145,26 @@ async function loadMindmapData(nodeId = null) {
} }
// Fehlerobjekt für die Benachrichtigung erstellen // Fehlerobjekt für die Benachrichtigung erstellen
const errorMessage = { const errorMessage = errorData.error || 'Unbekannter Fehler';
error: errorData.error || errorData.message || 'Unbekannter Fehler',
details: errorData.details || null
};
showUINotification(errorMessage, 'error'); showUINotification(errorMessage, 'error');
throw new Error(errorMessage.error); throw new Error(errorMessage);
} }
const data = await response.json(); const data = await response.json();
console.log('Geladene Mindmap-Daten:', data); console.log('Geladene Mindmap-Daten:', data);
if (!data.success) { if (!data.success) {
const errorMessage = { const errorMessage = data.error || 'Mindmap-Daten konnten nicht geladen werden';
error: data.error || 'Mindmap-Daten konnten nicht geladen werden',
details: data.details || null
};
showUINotification(errorMessage, 'error'); showUINotification(errorMessage, 'error');
throw new Error(errorMessage.error); throw new Error(errorMessage);
}
// Überprüfen, ob Nodes und Edges existieren
if (!data.nodes || !data.edges) {
const errorMessage = 'Ungültiges Datenformat: Nodes oder Edges fehlen';
showUINotification(errorMessage, 'error');
throw new Error(errorMessage);
} }
// Erfolgreiche Antwort // Erfolgreiche Antwort
@@ -175,17 +172,10 @@ async function loadMindmapData(nodeId = null) {
showUINotification('Mindmap-Daten erfolgreich geladen', 'success'); showUINotification('Mindmap-Daten erfolgreich geladen', 'success');
return data; return data;
} catch (error) { } catch (error) {
console.error('Fehler beim Laden der Mindmap-Daten:', { console.error('Fehler beim Laden der Mindmap-Daten:', error);
message: error.message,
stack: error.stack,
nodeId
});
// Stelle sicher, dass wir eine aussagekräftige Fehlermeldung haben // Stelle sicher, dass wir eine aussagekräftige Fehlermeldung haben
const errorMessage = { const errorMessage = error.message || 'Unbekannter Fehler beim Laden der Mindmap-Daten';
error: error.message || 'Unbekannter Fehler beim Laden der Mindmap-Daten',
details: error.stack
};
showUINotification(errorMessage, 'error'); showUINotification(errorMessage, 'error');
throw error; throw error;
@@ -754,615 +744,112 @@ function showFlash(message, type = 'info') {
* @param {number} duration - Die Anzeigedauer in Millisekunden (Standard: 3000) * @param {number} duration - Die Anzeigedauer in Millisekunden (Standard: 3000)
*/ */
function showUINotification(message, type = 'info', duration = 3000) { function showUINotification(message, type = 'info', duration = 3000) {
// Überprüfe und formatiere die Nachricht // Container erstellen, falls er nicht existiert
let displayMessage; let container = document.getElementById('notification-container');
if (typeof message === 'object') { if (!container) {
if (message.message) { container = document.createElement('div');
displayMessage = message.message; container.id = 'notification-container';
} else if (message.error) { container.style.position = 'fixed';
displayMessage = message.error; container.style.top = '1rem';
} else if (message.details) { container.style.right = '1rem';
displayMessage = message.details; container.style.zIndex = '1000';
} else { container.style.maxWidth = '400px';
console.error('Ungültiges Nachrichtenobjekt:', message);
displayMessage = 'Ein unbekannter Fehler ist aufgetreten';
}
} else if (typeof message === 'string') {
displayMessage = message;
} else {
console.error('Ungültige Nachricht für UI-Benachrichtigung:', message);
displayMessage = 'Ein unbekannter Fehler ist aufgetreten';
}
// Validiere den Typ
const validTypes = ['info', 'success', 'warning', 'error'];
if (!validTypes.includes(type)) {
console.warn(`Ungültiger Benachrichtigungstyp: ${type}. Verwende 'info' als Fallback.`);
type = 'info';
}
// Validiere die Dauer
if (typeof duration !== 'number' || duration < 1000 || duration > 10000) {
console.warn(`Ungültige Dauer: ${duration}ms. Verwende 3000ms als Fallback.`);
duration = 3000;
}
// Zeige die Benachrichtigung an
showFlash(displayMessage, type);
// Logging für Debugging-Zwecke
console.log(`UI-Benachrichtigung [${type}]:`, displayMessage);
}
// Hilfsfunktion zum Erstellen eines Flash-Containers, falls keiner existiert
function createFlashContainer() {
const container = document.createElement('div');
container.id = 'flash-messages';
container.className = 'fixed top-4 right-4 z-50 w-64';
document.body.appendChild(container); document.body.appendChild(container);
return container; }
}
// Funktion zum Laden der Subthemen // Benachrichtigung erstellen
async function loadSubthemes(node) { 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';
// 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';
}
// Nachrichteninhalt formatieren
let content = '';
if (typeof message === 'object' && message !== null) {
// Wenn es ein Fehler-Objekt ist
if (message.error) {
content = message.error;
if (message.details) {
content += `<br><small>${message.details}</small>`;
}
} else {
// Versuche, das Objekt zu stringifizieren
try { try {
// Prüfe zuerst, ob die Node gültig ist content = JSON.stringify(message);
if (!node || !node.id) {
console.error('Ungültige Node beim Laden der Subthemen');
showUINotification('Fehler: Ungültiger Knoten für Subthemen', 'error');
return;
}
// Zeige Ladebenachrichtigung
showUINotification('Lade Subthemen...', 'info');
console.log('Lade Subthemen für Node:', node.id());
// Lade die Daten für die Unterthemen
const data = await loadMindmapData(node.id());
if (!data || !data.nodes || !data.edges) {
throw new Error('Ungültiges Datenformat: Subthemen-Daten fehlen oder sind unvollständig');
}
// Markiere den aktuellen Knoten als erweitert
node.data('expanded', true);
// Finde den Mindmap-Container
let mindmapContainer = document.querySelector('.mindmap-container');
if (!mindmapContainer) {
// Falls der Container nicht existiert, versuche den cy-Container zu finden und erstelle einen Wrapper
const cyContainer = document.getElementById('cy');
if (!cyContainer) {
throw new Error('Mindmap-Container nicht gefunden');
}
// Erstelle einen Container für die Mindmap-Seiten
const parentElement = cyContainer.parentElement;
mindmapContainer = document.createElement('div');
mindmapContainer.className = 'mindmap-container';
parentElement.insertBefore(mindmapContainer, cyContainer);
parentElement.removeChild(cyContainer);
mindmapContainer.appendChild(cyContainer);
}
// Erstelle eine neue Seite für die Unterthemen
const newPage = document.createElement('div');
newPage.className = 'mindmap-page';
newPage.setAttribute('data-parent-node', node.id());
// Erstelle den Header der Seite
const header = document.createElement('div');
header.className = 'mindmap-header';
header.innerHTML = `
<button class="back-button" onclick="goBack()">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
</button>
<h2 class="mindmap-title">${node.data('label')}</h2>
<div class="mindmap-actions">
<button class="edit-button" onclick="enableMindmapEditing('${node.id()}')">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
<span>Bearbeiten</span>
</button>
</div>
`;
// Erstelle den Container für das neue Cytoscape
const newCyContainer = document.createElement('div');
newCyContainer.id = `cy-${node.id()}`;
newCyContainer.className = 'mindmap-view';
// Füge die Elemente zur Seite hinzu
newPage.appendChild(header);
newPage.appendChild(newCyContainer);
mindmapContainer.appendChild(newPage);
// Aktuelles Cy-Element ausblenden
if (cy && cy.container()) {
cy.container().style.display = 'none';
}
// Initialisiere das neue Cytoscape
const newCy = cytoscape({
container: newCyContainer,
elements: [
// Knoten
...data.nodes.map(node => ({
data: {
id: node.id,
label: node.name || node.label,
category: node.category,
description: node.description,
hasChildren: node.has_children,
expanded: false,
color: node.color_code || (node.category && mindmapConfig.categories[node.category]
? mindmapConfig.categories[node.category].color
: '#60a5fa'),
fontColor: '#ffffff',
fontSize: 16
}
})),
// Kanten
...data.edges.map(edge => ({
data: {
source: edge.source || edge.source_id,
target: edge.target || edge.target_id,
strength: edge.strength || 0.5
}
}))
],
style: [
{
selector: 'node',
style: mindmapStyles.node.base
},
{
selector: 'node[isCenter]',
style: mindmapStyles.node.center
},
{
selector: 'node:selected',
style: mindmapStyles.node.selected
},
{
selector: 'edge',
style: mindmapStyles.edge.base
}
],
layout: mindmapStyles.layout.base
});
// Füge neuronale Eigenschaften zu allen Knoten hinzu
newCy.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
});
});
// Füge synaptische Eigenschaften zu allen Kanten hinzu
newCy.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
});
});
// Event-Listener für die neue Mindmap
newCy.on('tap', 'node', async function(evt) {
const clickedNode = evt.target;
console.log('Node clicked in subtheme:', clickedNode.id(), 'hasChildren:', clickedNode.data('hasChildren'), 'expanded:', clickedNode.data('expanded'));
if (clickedNode.data('hasChildren') && !clickedNode.data('expanded')) {
await loadSubthemes(clickedNode);
}
});
// Starte neuronale Aktivitätssimulation für die neue Mindmap
startNeuralActivitySimulation(newCy);
// Speichere die Cytoscape-Instanz in einem globalen Array, damit wir sie später referenzieren können
if (!window.subthemeCyInstances) {
window.subthemeCyInstances = {};
}
window.subthemeCyInstances[node.id()] = newCy;
// Layout ausführen
newCy.layout(mindmapStyles.layout.base).run();
// Zeige die neue Seite an
newPage.style.display = 'block';
showUINotification('Subthemen erfolgreich geladen', 'success');
return true;
} catch (error) {
console.error('Fehler beim Laden der Subthemen:', error);
showUINotification({
error: 'Subthemen konnten nicht geladen werden',
details: error.message
}, 'error');
return false;
}
}
// Funktion zum Zurücknavigieren
function goBack() {
try {
// Finde die aktuell angezeigte Mindmap-Seite
const currentPage = document.querySelector('.mindmap-page:not([style*="display: none"])');
if (!currentPage) {
console.warn('Keine Mindmap-Seite gefunden, zu der zurückgekehrt werden kann');
return;
}
// Finde die übergeordnete Node ID
const parentNodeId = currentPage.getAttribute('data-parent-node');
if (!parentNodeId) {
console.warn('Keine übergeordnete Node-ID gefunden, zeige die Hauptmindmap an');
// Blende das aktuelle Cy-Element aus
if (window.cy && window.cy.container()) {
window.cy.container().style.display = 'block';
}
// Entferne die aktuelle Seite
currentPage.style.display = 'none';
setTimeout(() => {
if (currentPage.parentNode) {
currentPage.parentNode.removeChild(currentPage);
}
}, 300);
return;
}
console.log('Navigiere zurück von Knoten:', parentNodeId);
// Entferne die aktuelle Cytoscape-Instanz aus dem Array
if (window.subthemeCyInstances && window.subthemeCyInstances[parentNodeId]) {
try {
if (typeof window.subthemeCyInstances[parentNodeId].destroy === 'function') {
window.subthemeCyInstances[parentNodeId].destroy();
}
delete window.subthemeCyInstances[parentNodeId];
} catch (e) { } catch (e) {
console.error('Fehler beim Zerstören der Cytoscape-Instanz:', e); content = 'Objekt konnte nicht angezeigt werden';
} }
} }
} else {
// String oder andere primitive Typen
content = message;
}
// Prüfe, ob es eine übergeordnete Mindmap-Seite gibt notification.innerHTML = content;
const parentPage = document.querySelector(`.mindmap-page[data-parent-node]:not([data-parent-node="${parentNodeId}"])`);
if (parentPage) { // Schließen-Button
// Wenn eine übergeordnete Seite gefunden wurde, zeige sie an const closeButton = document.createElement('span');
parentPage.style.display = 'block'; closeButton.innerHTML = '&times;';
closeButton.style.position = 'absolute';
// Entferne die aktuelle Seite closeButton.style.top = '0.25rem';
currentPage.style.display = 'none'; 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(() => { setTimeout(() => {
if (currentPage.parentNode) { if (notification.parentNode === container) {
currentPage.parentNode.removeChild(currentPage); container.removeChild(notification);
} }
}, 300); }, 300);
} else { };
// Wenn keine übergeordnete Seite gefunden wurde, zeige die Hauptmindmap an notification.appendChild(closeButton);
if (window.cy && window.cy.container()) {
window.cy.container().style.display = 'block';
}
// Entferne die aktuelle Seite // Zur Seite hinzufügen
currentPage.style.display = 'none'; container.appendChild(notification);
// Animation starten
setTimeout(() => { setTimeout(() => {
if (currentPage.parentNode) { notification.style.opacity = '1';
currentPage.parentNode.removeChild(currentPage); notification.style.transform = 'translateY(0)';
}, 10);
// 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); }, 300);
} }
}, duration);
showUINotification('Zurück zur übergeordneten Mindmap', 'info');
} catch (error) {
console.error('Fehler bei der Rücknavigation:', error);
showUINotification('Fehler bei der Rücknavigation', 'error');
}
}
// CSS-Styles für die neue Seite
const style = document.createElement('style');
style.textContent = `
.mindmap-page {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: var(--bg-color, #1a1a1a);
z-index: 1000;
}
.mindmap-header {
display: flex;
align-items: center;
padding: 1rem;
background: rgba(0, 0, 0, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.back-button {
background: none;
border: none;
color: #fff;
cursor: pointer;
padding: 0.5rem;
margin-right: 1rem;
border-radius: 50%;
transition: background-color 0.3s;
}
.back-button:hover {
background: rgba(255, 255, 255, 0.1);
}
.mindmap-title {
color: #fff;
font-size: 1.5rem;
font-weight: 600;
margin: 0;
}
.mindmap-view {
width: 100%;
height: calc(100% - 4rem);
}
/* Neuronale Effekte */
.cy-container {
background: linear-gradient(45deg, #1a1a1a, #2a2a2a);
}
.cy-container node {
transition: all 0.3s ease;
}
.cy-container node:hover {
filter: brightness(1.2);
}
.cy-node-icon {
position: absolute;
pointer-events: none;
z-index: 1001;
color: #fff;
text-shadow: 0 2px 8px rgba(0,0,0,0.25);
}
/* Verbesserte Flash-Benachrichtigungen */
#flash-messages {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 0.5rem;
max-width: 24rem;
}
.flash-message {
padding: 1rem 1.25rem;
border-radius: 0.5rem;
background: rgba(17, 24, 39, 0.95);
color: #fff;
font-size: 0.875rem;
line-height: 1.25rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),
0 2px 4px -1px rgba(0, 0, 0, 0.06);
transform: translateX(120%);
opacity: 0;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.flash-message.show {
transform: translateX(0);
opacity: 1;
}
.flash-message.info {
border-left: 4px solid #3b82f6;
background: linear-gradient(to right, rgba(59, 130, 246, 0.1), rgba(17, 24, 39, 0.95));
}
.flash-message.success {
border-left: 4px solid #10b981;
background: linear-gradient(to right, rgba(16, 185, 129, 0.1), rgba(17, 24, 39, 0.95));
}
.flash-message.warning {
border-left: 4px solid #f59e0b;
background: linear-gradient(to right, rgba(245, 158, 11, 0.1), rgba(17, 24, 39, 0.95));
}
.flash-message.error {
border-left: 4px solid #ef4444;
background: linear-gradient(to right, rgba(239, 68, 68, 0.1), rgba(17, 24, 39, 0.95));
}
.flash-message::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg,
rgba(255, 255, 255, 0.1) 0%,
rgba(255, 255, 255, 0) 100%);
pointer-events: none;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.02); }
100% { transform: scale(1); }
}
.flash-message.show {
animation: pulse 0.3s ease-in-out;
}
/* Neuronale Effekte für Benachrichtigungen */
.flash-message.info:hover {
box-shadow: 0 0 15px rgba(59, 130, 246, 0.3);
}
.flash-message.success:hover {
box-shadow: 0 0 15px rgba(16, 185, 129, 0.3);
}
.flash-message.warning:hover {
box-shadow: 0 0 15px rgba(245, 158, 11, 0.3);
}
.flash-message.error:hover {
box-shadow: 0 0 15px rgba(239, 68, 68, 0.3);
}
`;
document.head.appendChild(style);
// Initialisiere die Mindmap beim Laden der Seite
document.addEventListener('DOMContentLoaded', initializeMindmap);
// Funktion zum Aktivieren des Bearbeitungsmodus
function enableMindmapEditing(nodeId) {
try {
console.log('Aktiviere Bearbeitungsmodus für Mindmap:', nodeId);
// Finde die relevante Cytoscape-Instanz
let targetCy;
if (nodeId) {
// Für Unterthemen
if (window.subthemeCyInstances && window.subthemeCyInstances[nodeId]) {
targetCy = window.subthemeCyInstances[nodeId];
} else {
throw new Error(`Cytoscape-Instanz für Node ${nodeId} nicht gefunden`);
}
} else {
// Für die Hauptmindmap
targetCy = window.cy;
}
if (!targetCy) {
throw new Error('Keine aktive Cytoscape-Instanz gefunden');
}
// Aktiviere Bearbeitungsmodus
toggleEditingMode(targetCy, true);
// Zeige Bearbeitungssteuerungen an
showEditingControls(nodeId);
showUINotification('Bearbeitungsmodus aktiviert', 'info');
} catch (error) {
console.error('Fehler beim Aktivieren des Bearbeitungsmodus:', error);
showUINotification('Fehler beim Aktivieren des Bearbeitungsmodus', 'error');
}
}
// Funktion zum Deaktivieren des Bearbeitungsmodus
function disableMindmapEditing(nodeId) {
try {
console.log('Deaktiviere Bearbeitungsmodus für Mindmap:', nodeId);
// Finde die relevante Cytoscape-Instanz
let targetCy;
if (nodeId) {
// Für Unterthemen
if (window.subthemeCyInstances && window.subthemeCyInstances[nodeId]) {
targetCy = window.subthemeCyInstances[nodeId];
}
} else {
// Für die Hauptmindmap
targetCy = window.cy;
}
if (!targetCy) {
throw new Error('Keine aktive Cytoscape-Instanz gefunden');
}
// Deaktiviere Bearbeitungsmodus
toggleEditingMode(targetCy, false);
// Verstecke Bearbeitungssteuerungen
hideEditingControls(nodeId);
showUINotification('Bearbeitungsmodus deaktiviert', 'info');
} catch (error) {
console.error('Fehler beim Deaktivieren des Bearbeitungsmodus:', error);
showUINotification('Fehler beim Deaktivieren des Bearbeitungsmodus', 'error');
}
}
// Funktion zum Umschalten des Bearbeitungsmodus
function toggleEditingMode(cy, enabled) {
if (!cy) return;
if (enabled) {
// Mache Knoten beweglich
cy.nodes().ungrabify(false);
// Ändere den Cursor-Stil
cy.container().classList.add('editing-mode');
// Aktiviere Ziehen und Ablegen
cy.on('dragfree', 'node', function(event) {
const node = event.target;
console.log('Node verschoben:', node.id(), node.position());
// Hier könnte man die neue Position in der Datenbank speichern
});
// Aktiviere Doppelklick zum Bearbeiten
cy.on('dblclick', 'node', function(event) {
const node = event.target;
editNodeProperties(node);
});
// Aktiviere Rechtsklick-Menü
cy.on('cxttap', 'node', function(event) {
const node = event.target;
showNodeContextMenu(node, event.renderedPosition);
});
// Aktiviere Rechtsklick auf leeren Bereich zum Hinzufügen neuer Knoten
cy.on('cxttap', function(event) {
if (event.target === cy) {
showAddNodeMenu(event.renderedPosition);
}
});
} else {
// Deaktiviere Bearbeitungsfunktionen
cy.nodes().grabify();
cy.container().classList.remove('editing-mode');
cy.removeListener('dragfree');
cy.removeListener('dblclick');
cy.removeListener('cxttap');
} }
} }

View File

@@ -31,6 +31,9 @@
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
border-bottom: 1px solid rgba(255, 255, 255, 0.1); border-bottom: 1px solid rgba(255, 255, 255, 0.1);
z-index: 10; z-index: 10;
display: flex;
justify-content: space-between;
align-items: center;
} }
.mindmap-title { .mindmap-title {
@@ -43,6 +46,47 @@
-webkit-text-fill-color: transparent; -webkit-text-fill-color: transparent;
} }
/* Aktionsmenü im Header */
.mindmap-actions {
display: flex;
gap: 0.75rem;
}
.action-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: rgba(255, 255, 255, 0.1);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 0.5rem;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.3s ease;
}
.action-button:hover {
background: rgba(255, 255, 255, 0.2);
}
.action-button.primary {
background: rgba(139, 92, 246, 0.3);
}
.action-button.primary:hover {
background: rgba(139, 92, 246, 0.5);
}
.action-button.danger {
background: rgba(220, 38, 38, 0.3);
}
.action-button.danger:hover {
background: rgba(220, 38, 38, 0.5);
}
/* Kontrollpanel */ /* Kontrollpanel */
.control-panel { .control-panel {
position: absolute; position: absolute;
@@ -79,6 +123,85 @@
margin-right: 0.75rem; margin-right: 0.75rem;
} }
/* CRUD Panel */
.crud-panel {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
background: rgba(15, 23, 42, 0.9);
border-radius: 1rem;
padding: 1rem;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
z-index: 10;
border: 1px solid rgba(255, 255, 255, 0.1);
display: flex;
gap: 0.75rem;
backdrop-filter: blur(8px);
}
.crud-button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 4rem;
height: 4rem;
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 0.75rem;
color: white;
cursor: pointer;
transition: all 0.3s ease;
}
.crud-button:hover {
background: rgba(255, 255, 255, 0.2);
transform: translateY(-5px);
}
.crud-button i {
font-size: 1.25rem;
margin-bottom: 0.25rem;
}
.crud-button span {
font-size: 0.7rem;
text-align: center;
}
.crud-button.create {
background: rgba(16, 185, 129, 0.3);
}
.crud-button.create:hover {
background: rgba(16, 185, 129, 0.5);
}
.crud-button.edit {
background: rgba(245, 158, 11, 0.3);
}
.crud-button.edit:hover {
background: rgba(245, 158, 11, 0.5);
}
.crud-button.delete {
background: rgba(220, 38, 38, 0.3);
}
.crud-button.delete:hover {
background: rgba(220, 38, 38, 0.5);
}
.crud-button.save {
background: rgba(59, 130, 246, 0.3);
}
.crud-button.save:hover {
background: rgba(59, 130, 246, 0.5);
}
/* Info-Panel */ /* Info-Panel */
.info-panel { .info-panel {
position: absolute; position: absolute;
@@ -157,6 +280,90 @@
.pulse { .pulse {
animation: pulse 2s infinite; animation: pulse 2s infinite;
} }
/* Ladeanzeige */
.loader {
border: 4px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
border-top: 4px solid #60a5fa;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
position: absolute;
top: 50%;
left: 50%;
margin-top: -20px;
margin-left: -20px;
z-index: 5;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Status-Meldung */
.status-message {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(15, 23, 42, 0.9);
padding: 1rem 2rem;
border-radius: 0.5rem;
color: white;
font-size: 1rem;
z-index: 15;
text-align: center;
max-width: 80%;
}
/* Bearbeitungsmodus-Hinweis */
.edit-mode-indicator {
position: fixed;
bottom: 1rem;
left: 1rem;
background: rgba(245, 158, 11, 0.8);
color: white;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
font-size: 0.9rem;
z-index: 1000;
display: none;
}
.edit-mode-indicator.active {
display: flex;
align-items: center;
gap: 0.5rem;
}
/* Kontext-Menü */
.context-menu {
position: absolute;
background: rgba(30, 41, 59, 0.95);
border-radius: 8px;
padding: 8px 0;
min-width: 160px;
z-index: 2000;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.context-menu-item {
padding: 8px 16px;
color: white;
cursor: pointer;
transition: background-color 0.2s ease;
display: flex;
align-items: center;
gap: 8px;
}
.context-menu-item:hover {
background: rgba(255, 255, 255, 0.1);
}
</style> </style>
{% endblock %} {% endblock %}
@@ -165,6 +372,34 @@
<!-- Header --> <!-- Header -->
<div class="mindmap-header"> <div class="mindmap-header">
<h1 class="mindmap-title">Interaktive Wissenslandkarte</h1> <h1 class="mindmap-title">Interaktive Wissenslandkarte</h1>
<!-- Aktionsmenü -->
<div class="mindmap-actions">
<button id="toggleEditMode" class="action-button primary">
<i class="fas fa-edit"></i>
<span>Bearbeiten</span>
</button>
<button id="saveChanges" class="action-button" style="display: none;">
<i class="fas fa-save"></i>
<span>Speichern</span>
</button>
<button id="cancelEdit" class="action-button danger" style="display: none;">
<i class="fas fa-times"></i>
<span>Abbrechen</span>
</button>
</div>
</div>
<!-- Ladeanzeige -->
<div id="loader" class="loader"></div>
<!-- Status-Meldung -->
<div id="statusMessage" class="status-message" style="display: none;">Lade Mindmap...</div>
<!-- Bearbeitungsmodus-Hinweis -->
<div id="editModeIndicator" class="edit-mode-indicator">
<i class="fas fa-pencil-alt"></i>
<span>Bearbeitungsmodus</span>
</div> </div>
<!-- Hauptvisualisierung --> <!-- Hauptvisualisierung -->
@@ -190,6 +425,30 @@
</button> </button>
</div> </div>
<!-- CRUD Buttons (anfänglich ausgeblendet) -->
<div id="crudPanel" class="crud-panel" style="display: none;">
<button id="createNode" class="crud-button create">
<i class="fas fa-plus-circle"></i>
<span>Knoten erstellen</span>
</button>
<button id="createEdge" class="crud-button create">
<i class="fas fa-link"></i>
<span>Verbindung</span>
</button>
<button id="editNode" class="crud-button edit" disabled>
<i class="fas fa-edit"></i>
<span>Bearbeiten</span>
</button>
<button id="deleteElement" class="crud-button delete" disabled>
<i class="fas fa-trash-alt"></i>
<span>Löschen</span>
</button>
<button id="saveMindmap" class="crud-button save">
<i class="fas fa-save"></i>
<span>Speichern</span>
</button>
</div>
<!-- Info-Panel --> <!-- Info-Panel -->
<div id="infoPanel" class="info-panel"> <div id="infoPanel" class="info-panel">
<h3 class="info-title">Knotendetails</h3> <h3 class="info-title">Knotendetails</h3>
@@ -199,23 +458,23 @@
<!-- Kategorie-Legende --> <!-- Kategorie-Legende -->
<div id="categoryLegend" class="category-legend"> <div id="categoryLegend" class="category-legend">
<div class="category-item"> <div class="category-item">
<div class="category-color" style="background-color: #60a5fa;"></div> <div class="category-color" style="background-color: #9F7AEA;"></div>
<span>Philosophie</span> <span>Philosophie</span>
</div> </div>
<div class="category-item"> <div class="category-item">
<div class="category-color" style="background-color: #8b5cf6;"></div> <div class="category-color" style="background-color: #60A5FA;"></div>
<span>Wissenschaft</span> <span>Wissenschaft</span>
</div> </div>
<div class="category-item"> <div class="category-item">
<div class="category-color" style="background-color: #10b981;"></div> <div class="category-color" style="background-color: #10B981;"></div>
<span>Technologie</span> <span>Technologie</span>
</div> </div>
<div class="category-item"> <div class="category-item">
<div class="category-color" style="background-color: #f59e0b;"></div> <div class="category-color" style="background-color: #F59E0B;"></div>
<span>Künste</span> <span>Künste</span>
</div> </div>
<div class="category-item"> <div class="category-item">
<div class="category-color" style="background-color: #ef4444;"></div> <div class="category-color" style="background-color: #EF4444;"></div>
<span>Psychologie</span> <span>Psychologie</span>
</div> </div>
</div> </div>
@@ -229,4 +488,256 @@
<!-- Unsere JavaScript-Dateien --> <!-- Unsere JavaScript-Dateien -->
<script src="{{ url_for('static', filename='js/update_mindmap.js') }}"></script> <script src="{{ url_for('static', filename='js/update_mindmap.js') }}"></script>
<!-- Initialisierung -->
<script>
document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded Event ausgelöst');
const cyContainer = document.getElementById('cy');
const loader = document.getElementById('loader');
const statusMessage = document.getElementById('statusMessage');
const crudPanel = document.getElementById('crudPanel');
const editModeIndicator = document.getElementById('editModeIndicator');
// CRUD Buttons
const createNodeBtn = document.getElementById('createNode');
const createEdgeBtn = document.getElementById('createEdge');
const editNodeBtn = document.getElementById('editNode');
const deleteElementBtn = document.getElementById('deleteElement');
const saveMindmapBtn = document.getElementById('saveMindmap');
// Header Action Buttons
const toggleEditModeBtn = document.getElementById('toggleEditMode');
const saveChangesBtn = document.getElementById('saveChanges');
const cancelEditBtn = document.getElementById('cancelEdit');
let isEditMode = false;
let selectedElement = null;
if (cyContainer) {
console.log('Container gefunden:', cyContainer);
// Loader und Statusmeldung anzeigen
loader.style.display = 'block';
statusMessage.textContent = 'Lade Mindmap...';
statusMessage.style.display = 'block';
// Prüfen, ob Cytoscape vorhanden ist
if (typeof cytoscape !== 'undefined') {
console.log('Cytoscape ist verfügbar');
// Initialisieren der Mindmap
initializeMindmap().then(() => {
// Erfolg: Loader und Statusmeldung ausblenden
loader.style.display = 'none';
statusMessage.style.display = 'none';
// Event-Listener für Knotenauswahl
window.cy.on('select', 'node', function(event) {
selectedElement = event.target;
editNodeBtn.disabled = false;
deleteElementBtn.disabled = false;
// Knotendetails im Info-Panel anzeigen
showNodeInfo(selectedElement);
});
window.cy.on('select', 'edge', function(event) {
selectedElement = event.target;
editNodeBtn.disabled = true;
deleteElementBtn.disabled = false;
});
window.cy.on('unselect', function() {
selectedElement = null;
editNodeBtn.disabled = true;
deleteElementBtn.disabled = true;
// Info-Panel ausblenden
hideNodeInfo();
});
// Rechtsklick-Menü
window.cy.on('cxttap', 'node', function(event) {
// Kontextmenü für Knoten anzeigen
if (isEditMode) {
const node = event.target;
const position = event.renderedPosition;
showNodeContextMenu(node, {
x: event.originalEvent.clientX,
y: event.originalEvent.clientY
});
event.preventDefault();
}
});
window.cy.on('cxttap', function(event) {
// Kontextmenü zum Hinzufügen eines Knotens
if (isEditMode && event.target === window.cy) {
showAddNodeMenu({
x: event.originalEvent.clientX,
y: event.originalEvent.clientY
});
event.preventDefault();
}
});
}).catch(error => {
// Fehler: Fehlermeldung anzeigen
console.error('Mindmap-Initialisierung fehlgeschlagen', error);
loader.style.display = 'none';
statusMessage.textContent = 'Mindmap konnte nicht initialisiert werden: ' + error.message;
statusMessage.style.backgroundColor = 'rgba(220, 38, 38, 0.9)';
statusMessage.style.display = 'block';
});
} else {
console.error('Cytoscape ist nicht verfügbar');
loader.style.display = 'none';
statusMessage.textContent = 'Cytoscape-Bibliothek konnte nicht geladen werden';
statusMessage.style.backgroundColor = 'rgba(220, 38, 38, 0.9)';
statusMessage.style.display = 'block';
}
} else {
console.error('Container #cy nicht gefunden');
}
// Bearbeitungsmodus umschalten
toggleEditModeBtn.addEventListener('click', function() {
isEditMode = !isEditMode;
if (isEditMode) {
// Bearbeitungsmodus aktivieren
crudPanel.style.display = 'flex';
editModeIndicator.classList.add('active');
toggleEditModeBtn.style.display = 'none';
saveChangesBtn.style.display = 'inline-flex';
cancelEditBtn.style.display = 'inline-flex';
window.cy.container().classList.add('editing-mode');
// Aktiviere Knotenbewegung (dragging)
window.cy.nodes().unlock();
} else {
// Bearbeitungsmodus deaktivieren
crudPanel.style.display = 'none';
editModeIndicator.classList.remove('active');
toggleEditModeBtn.style.display = 'inline-flex';
saveChangesBtn.style.display = 'none';
cancelEditBtn.style.display = 'none';
window.cy.container().classList.remove('editing-mode');
// Deaktiviere Knotenbewegung
window.cy.nodes().lock();
}
});
// Änderungen speichern
saveChangesBtn.addEventListener('click', function() {
saveMindmapChanges(window.cy);
});
// Bearbeitungsmodus abbrechen
cancelEditBtn.addEventListener('click', function() {
if (confirm('Möchten Sie den Bearbeitungsmodus wirklich verlassen? Nicht gespeicherte Änderungen gehen verloren.')) {
isEditMode = false;
crudPanel.style.display = 'none';
editModeIndicator.classList.remove('active');
toggleEditModeBtn.style.display = 'inline-flex';
saveChangesBtn.style.display = 'none';
cancelEditBtn.style.display = 'none';
window.cy.container().classList.remove('editing-mode');
// Neuinitialisierung der Mindmap
initializeMindmap().then(() => {
loader.style.display = 'none';
statusMessage.style.display = 'none';
});
}
});
// CRUD-Funktionen
createNodeBtn.addEventListener('click', function() {
if (isEditMode) {
addNewNode(window.cy);
}
});
createEdgeBtn.addEventListener('click', function() {
if (isEditMode) {
enableEdgeCreationMode(window.cy);
}
});
editNodeBtn.addEventListener('click', function() {
if (isEditMode && selectedElement && selectedElement.isNode()) {
editNodeProperties(selectedElement);
}
});
deleteElementBtn.addEventListener('click', function() {
if (isEditMode && selectedElement) {
if (selectedElement.isNode()) {
deleteNode(selectedElement);
} else if (selectedElement.isEdge()) {
if (confirm('Möchten Sie diese Verbindung wirklich löschen?')) {
selectedElement.remove();
}
}
}
});
saveMindmapBtn.addEventListener('click', function() {
if (isEditMode) {
saveMindmapChanges(window.cy);
}
});
// Zoom-Funktionen
document.getElementById('zoomIn').addEventListener('click', function() {
if (window.cy) window.cy.zoom(window.cy.zoom() * 1.2);
});
document.getElementById('zoomOut').addEventListener('click', function() {
if (window.cy) window.cy.zoom(window.cy.zoom() * 0.8);
});
document.getElementById('resetView').addEventListener('click', function() {
if (window.cy) window.cy.fit();
});
document.getElementById('toggleLegend').addEventListener('click', function() {
const legend = document.getElementById('categoryLegend');
legend.style.display = legend.style.display === 'none' ? 'flex' : 'none';
});
// Funktionen für Knoteninfo-Panel
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');
const data = node.data();
infoTitle.textContent = data.label || 'Knotendetails';
let contentHTML = `
<p><strong>Kategorie:</strong> ${data.category || 'Nicht kategorisiert'}</p>
<p><strong>Beschreibung:</strong> ${data.description || 'Keine Beschreibung verfügbar'}</p>
`;
if (data.hasChildren) {
contentHTML += `<p><i class="fas fa-sitemap"></i> Hat Unterknoten</p>`;
}
infoContent.innerHTML = contentHTML;
infoPanel.classList.add('visible');
}
function hideNodeInfo() {
const infoPanel = document.getElementById('infoPanel');
infoPanel.classList.remove('visible');
}
});
</script>
{% endblock %} {% endblock %}