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