Compare commits
22 Commits
dc96252013
...
d3405a7031
| Author | SHA1 | Date | |
|---|---|---|---|
| d3405a7031 | |||
| 5793902e47 | |||
| e73ccd7e80 | |||
| e6784b712d | |||
| 35b5f321d4 | |||
| b68f65cc76 | |||
| 3a2f721f63 | |||
| 5933195196 | |||
| beccfa25a6 | |||
| bc5cef3ba8 | |||
| b867af9c8b | |||
| ee04432a49 | |||
| bbcee7f610 | |||
| 1eb47fc230 | |||
| 2921c5a824 | |||
| c98e238841 | |||
| af30a208ca | |||
| 2e2f35ccc1 | |||
| fd293e53e1 | |||
| 2b19cb000b | |||
| 3aefe6c5e6 | |||
| c7b87dc643 |
Binary file not shown.
Binary file not shown.
495
app.py
495
app.py
@@ -2062,6 +2062,320 @@ def dummy_network_bg():
|
||||
"""Leere Antwort für die nicht mehr verwendeten Netzwerk-Hintergrundbilder."""
|
||||
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
|
||||
@app.route('/admin/reload-env', methods=['POST'])
|
||||
@admin_required
|
||||
@@ -2343,184 +2657,3 @@ if __name__ == '__main__':
|
||||
db.create_all()
|
||||
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.
130
init_db.py
130
init_db.py
@@ -1,19 +1,29 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- 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 sqlite3
|
||||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
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
|
||||
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
|
||||
|
||||
# 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)
|
||||
|
||||
def init_db():
|
||||
@@ -69,45 +79,111 @@ def create_default_users():
|
||||
|
||||
def create_default_categories():
|
||||
"""Erstellt die Standardkategorien für die Mindmap"""
|
||||
categories = [
|
||||
# Hauptkategorien
|
||||
main_categories = [
|
||||
{
|
||||
'name': 'Konzept',
|
||||
'description': 'Abstrakte Ideen und theoretische Konzepte',
|
||||
'color_code': '#6366f1',
|
||||
'icon': 'lightbulb'
|
||||
"name": "Philosophie",
|
||||
"description": "Philosophisches Denken und Konzepte",
|
||||
"color_code": "#9F7AEA",
|
||||
"icon": "fa-brain"
|
||||
},
|
||||
{
|
||||
'name': 'Technologie',
|
||||
'description': 'Hardware, Software, Tools und Plattformen',
|
||||
'color_code': '#10b981',
|
||||
'icon': 'cpu'
|
||||
"name": "Wissenschaft",
|
||||
"description": "Wissenschaftliche Disziplinen und Erkenntnisse",
|
||||
"color_code": "#60A5FA",
|
||||
"icon": "fa-flask"
|
||||
},
|
||||
{
|
||||
'name': 'Prozess',
|
||||
'description': 'Workflows, Methodologien und Vorgehensweisen',
|
||||
'color_code': '#f59e0b',
|
||||
'icon': 'git-branch'
|
||||
"name": "Technologie",
|
||||
"description": "Technologische Entwicklungen und Anwendungen",
|
||||
"color_code": "#10B981",
|
||||
"icon": "fa-microchip"
|
||||
},
|
||||
{
|
||||
'name': 'Person',
|
||||
'description': 'Personen, Teams und Organisationen',
|
||||
'color_code': '#ec4899',
|
||||
'icon': 'user'
|
||||
"name": "Künste",
|
||||
"description": "Künstlerische Ausdrucksformen und Werke",
|
||||
"color_code": "#F59E0B",
|
||||
"icon": "fa-palette"
|
||||
},
|
||||
{
|
||||
'name': 'Dokument',
|
||||
'description': 'Dokumentationen, Referenzen und Ressourcen',
|
||||
'color_code': '#3b82f6',
|
||||
'icon': 'file-text'
|
||||
"name": "Psychologie",
|
||||
"description": "Mentale Prozesse und Verhaltensweisen",
|
||||
"color_code": "#EF4444",
|
||||
"icon": "fa-brain"
|
||||
}
|
||||
]
|
||||
|
||||
for cat_data in categories:
|
||||
# Hauptkategorien erstellen
|
||||
category_map = {}
|
||||
for cat_data in main_categories:
|
||||
category = Category(**cat_data)
|
||||
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()
|
||||
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():
|
||||
"""Erstellt eine Beispiel-Mindmap mit Knoten und Beziehungen"""
|
||||
|
||||
5957
logs/app.log
5957
logs/app.log
File diff suppressed because it is too large
Load Diff
@@ -136,11 +136,7 @@ async function loadMindmapData(nodeId = null) {
|
||||
let errorData;
|
||||
try {
|
||||
errorData = await response.json();
|
||||
console.log('API-Fehler Details:', {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
errorData
|
||||
});
|
||||
console.log('API-Fehler Details:', errorData);
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Parsen der Fehlerantwort:', e);
|
||||
errorData = {
|
||||
@@ -149,25 +145,26 @@ async function loadMindmapData(nodeId = null) {
|
||||
}
|
||||
|
||||
// Fehlerobjekt für die Benachrichtigung erstellen
|
||||
const errorMessage = {
|
||||
error: errorData.error || errorData.message || 'Unbekannter Fehler',
|
||||
details: errorData.details || null
|
||||
};
|
||||
const errorMessage = errorData.error || 'Unbekannter Fehler';
|
||||
|
||||
showUINotification(errorMessage, 'error');
|
||||
throw new Error(errorMessage.error);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log('Geladene Mindmap-Daten:', data);
|
||||
|
||||
if (!data.success) {
|
||||
const errorMessage = {
|
||||
error: data.error || 'Mindmap-Daten konnten nicht geladen werden',
|
||||
details: data.details || null
|
||||
};
|
||||
const errorMessage = data.error || 'Mindmap-Daten konnten nicht geladen werden';
|
||||
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
|
||||
@@ -175,17 +172,10 @@ async function loadMindmapData(nodeId = null) {
|
||||
showUINotification('Mindmap-Daten erfolgreich geladen', 'success');
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Mindmap-Daten:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
nodeId
|
||||
});
|
||||
console.error('Fehler beim Laden der Mindmap-Daten:', error);
|
||||
|
||||
// Stelle sicher, dass wir eine aussagekräftige Fehlermeldung haben
|
||||
const errorMessage = {
|
||||
error: error.message || 'Unbekannter Fehler beim Laden der Mindmap-Daten',
|
||||
details: error.stack
|
||||
};
|
||||
const errorMessage = error.message || 'Unbekannter Fehler beim Laden der Mindmap-Daten';
|
||||
|
||||
showUINotification(errorMessage, 'error');
|
||||
throw error;
|
||||
@@ -754,615 +744,112 @@ function showFlash(message, type = 'info') {
|
||||
* @param {number} duration - Die Anzeigedauer in Millisekunden (Standard: 3000)
|
||||
*/
|
||||
function showUINotification(message, type = 'info', duration = 3000) {
|
||||
// Überprüfe und formatiere die Nachricht
|
||||
let displayMessage;
|
||||
if (typeof message === 'object') {
|
||||
if (message.message) {
|
||||
displayMessage = message.message;
|
||||
} else if (message.error) {
|
||||
displayMessage = message.error;
|
||||
} else if (message.details) {
|
||||
displayMessage = message.details;
|
||||
} else {
|
||||
console.error('Ungültiges Nachrichtenobjekt:', message);
|
||||
displayMessage = 'Ein unbekannter Fehler ist aufgetreten';
|
||||
}
|
||||
} else if (typeof message === 'string') {
|
||||
displayMessage = message;
|
||||
// Container erstellen, falls er nicht existiert
|
||||
let container = document.getElementById('notification-container');
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
container.id = 'notification-container';
|
||||
container.style.position = 'fixed';
|
||||
container.style.top = '1rem';
|
||||
container.style.right = '1rem';
|
||||
container.style.zIndex = '1000';
|
||||
container.style.maxWidth = '400px';
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
// Benachrichtigung erstellen
|
||||
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 {
|
||||
console.error('Ungültige Nachricht für UI-Benachrichtigung:', message);
|
||||
displayMessage = 'Ein unbekannter Fehler ist aufgetreten';
|
||||
notification.style.backgroundColor = '#3B82F6';
|
||||
notification.style.color = '#ffffff';
|
||||
}
|
||||
|
||||
// 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);
|
||||
return container;
|
||||
}
|
||||
|
||||
// Funktion zum Laden der Subthemen
|
||||
async function loadSubthemes(node) {
|
||||
try {
|
||||
// Prüfe zuerst, ob die Node gültig ist
|
||||
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');
|
||||
|
||||
// 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>`;
|
||||
}
|
||||
|
||||
// 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]) {
|
||||
} else {
|
||||
// Versuche, das Objekt zu stringifizieren
|
||||
try {
|
||||
if (typeof window.subthemeCyInstances[parentNodeId].destroy === 'function') {
|
||||
window.subthemeCyInstances[parentNodeId].destroy();
|
||||
}
|
||||
delete window.subthemeCyInstances[parentNodeId];
|
||||
content = JSON.stringify(message);
|
||||
} catch (e) {
|
||||
console.error('Fehler beim Zerstören der Cytoscape-Instanz:', e);
|
||||
content = 'Objekt konnte nicht angezeigt werden';
|
||||
}
|
||||
}
|
||||
|
||||
// Prüfe, ob es eine übergeordnete Mindmap-Seite gibt
|
||||
const parentPage = document.querySelector(`.mindmap-page[data-parent-node]:not([data-parent-node="${parentNodeId}"])`);
|
||||
|
||||
if (parentPage) {
|
||||
// Wenn eine übergeordnete Seite gefunden wurde, zeige sie an
|
||||
parentPage.style.display = 'block';
|
||||
|
||||
// Entferne die aktuelle Seite
|
||||
currentPage.style.display = 'none';
|
||||
setTimeout(() => {
|
||||
if (currentPage.parentNode) {
|
||||
currentPage.parentNode.removeChild(currentPage);
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
// Wenn keine übergeordnete Seite gefunden wurde, zeige die Hauptmindmap an
|
||||
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);
|
||||
}
|
||||
|
||||
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');
|
||||
// String oder andere primitive Typen
|
||||
content = message;
|
||||
}
|
||||
|
||||
notification.innerHTML = content;
|
||||
|
||||
// Schließen-Button
|
||||
const closeButton = document.createElement('span');
|
||||
closeButton.innerHTML = '×';
|
||||
closeButton.style.position = 'absolute';
|
||||
closeButton.style.top = '0.25rem';
|
||||
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(() => {
|
||||
if (notification.parentNode === container) {
|
||||
container.removeChild(notification);
|
||||
}
|
||||
}, 300);
|
||||
};
|
||||
notification.appendChild(closeButton);
|
||||
|
||||
// Zur Seite hinzufügen
|
||||
container.appendChild(notification);
|
||||
|
||||
// Animation starten
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '1';
|
||||
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);
|
||||
}
|
||||
}, duration);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mindmap-title {
|
||||
@@ -43,6 +46,47 @@
|
||||
-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 */
|
||||
.control-panel {
|
||||
position: absolute;
|
||||
@@ -79,6 +123,85 @@
|
||||
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 {
|
||||
position: absolute;
|
||||
@@ -157,6 +280,90 @@
|
||||
.pulse {
|
||||
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>
|
||||
{% endblock %}
|
||||
|
||||
@@ -165,6 +372,34 @@
|
||||
<!-- Header -->
|
||||
<div class="mindmap-header">
|
||||
<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>
|
||||
|
||||
<!-- Hauptvisualisierung -->
|
||||
@@ -190,6 +425,30 @@
|
||||
</button>
|
||||
</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 -->
|
||||
<div id="infoPanel" class="info-panel">
|
||||
<h3 class="info-title">Knotendetails</h3>
|
||||
@@ -199,23 +458,23 @@
|
||||
<!-- Kategorie-Legende -->
|
||||
<div id="categoryLegend" class="category-legend">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -229,4 +488,256 @@
|
||||
|
||||
<!-- Unsere JavaScript-Dateien -->
|
||||
<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 %}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user