Merge branches 'main' and 'main' of https://git.clickcandit.com/marwinm/website
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,406 +0,0 @@
|
||||
/**
|
||||
* Mindmap Interaction Enhancement
|
||||
* Verbessert die Interaktion mit der Mindmap und steuert die Seitenleisten-Anzeige
|
||||
*/
|
||||
|
||||
// Stellt sicher, dass das Dokument geladen ist, bevor Aktionen ausgeführt werden
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('Mindmap-Interaktionsverbesserungen werden initialisiert...');
|
||||
|
||||
// Auf das Laden der Mindmap warten
|
||||
document.addEventListener('mindmap-loaded', setupInteractionEnhancements);
|
||||
|
||||
// Sofortiges Setup für statische Interaktionen
|
||||
setupStaticInteractions();
|
||||
|
||||
// Direkten Event-Listener für Knotenauswahl einrichten
|
||||
setupNodeSelectionListener();
|
||||
});
|
||||
|
||||
// Richtet grundlegende statische Interaktionen ein
|
||||
function setupStaticInteractions() {
|
||||
// Vergrößert die Mindmap auf Vollbildmodus, wenn der entsprechende Button geklickt wird
|
||||
const fullscreenBtn = document.getElementById('fullscreen-btn');
|
||||
if (fullscreenBtn) {
|
||||
fullscreenBtn.addEventListener('click', toggleFullscreen);
|
||||
}
|
||||
|
||||
// Initialisiert die Hover-Effekte für die Seitenleisten-Panels
|
||||
initializePanelEffects();
|
||||
}
|
||||
|
||||
// Richtet erweiterte Interaktionen mit der geladenen Mindmap ein
|
||||
function setupInteractionEnhancements() {
|
||||
console.log('Mindmap geladen - verbesserte Interaktionen werden eingerichtet');
|
||||
|
||||
// Zugriff auf die Mindmap-Instanz
|
||||
const mindmap = window.mindmapInstance;
|
||||
if (!mindmap) {
|
||||
console.warn('Mindmap-Instanz nicht gefunden!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verbesserte Zoom-Kontrollen
|
||||
setupZoomControls(mindmap);
|
||||
|
||||
// Verhindere, dass der Browser die Seite scrollt, wenn über der Mindmap gezoomt wird
|
||||
preventScrollWhileZooming();
|
||||
|
||||
// Tastaturkürzel für Mindmap-Interaktionen
|
||||
setupKeyboardShortcuts(mindmap);
|
||||
|
||||
// Verbesserte Touch-Gesten für mobile Geräte
|
||||
setupTouchInteractions(mindmap);
|
||||
}
|
||||
|
||||
// Verhindert Browser-Scrolling während Zoom in der Mindmap
|
||||
function preventScrollWhileZooming() {
|
||||
const cyContainer = document.getElementById('cy');
|
||||
if (cyContainer) {
|
||||
cyContainer.addEventListener('wheel', function(e) {
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault(); // Verhindert Browser-Zoom bei Ctrl+Wheel
|
||||
}
|
||||
}, { passive: false });
|
||||
}
|
||||
}
|
||||
|
||||
// Richtet verbesserte Zoom-Kontrollen ein
|
||||
function setupZoomControls(mindmap) {
|
||||
const zoomInBtn = document.getElementById('zoom-in-btn');
|
||||
const zoomOutBtn = document.getElementById('zoom-out-btn');
|
||||
const resetZoomBtn = document.getElementById('reset-btn');
|
||||
|
||||
if (zoomInBtn) {
|
||||
zoomInBtn.addEventListener('click', function() {
|
||||
mindmap.svg.transition()
|
||||
.duration(300)
|
||||
.call(mindmap.svg.zoom().scaleBy, 1.4);
|
||||
});
|
||||
}
|
||||
|
||||
if (zoomOutBtn) {
|
||||
zoomOutBtn.addEventListener('click', function() {
|
||||
mindmap.svg.transition()
|
||||
.duration(300)
|
||||
.call(mindmap.svg.zoom().scaleBy, 0.7);
|
||||
});
|
||||
}
|
||||
|
||||
if (resetZoomBtn) {
|
||||
resetZoomBtn.addEventListener('click', function() {
|
||||
mindmap.svg.transition()
|
||||
.duration(500)
|
||||
.call(mindmap.svg.zoom().transform, d3.zoomIdentity);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Vollbildmodus umschalten
|
||||
function toggleFullscreen() {
|
||||
const mindmapContainer = document.querySelector('.mindmap-container');
|
||||
|
||||
if (!mindmapContainer) return;
|
||||
|
||||
if (!document.fullscreenElement) {
|
||||
// Vollbildmodus aktivieren
|
||||
if (mindmapContainer.requestFullscreen) {
|
||||
mindmapContainer.requestFullscreen();
|
||||
} else if (mindmapContainer.mozRequestFullScreen) {
|
||||
mindmapContainer.mozRequestFullScreen();
|
||||
} else if (mindmapContainer.webkitRequestFullscreen) {
|
||||
mindmapContainer.webkitRequestFullscreen();
|
||||
} else if (mindmapContainer.msRequestFullscreen) {
|
||||
mindmapContainer.msRequestFullscreen();
|
||||
}
|
||||
|
||||
// Icon ändern
|
||||
const fullscreenBtn = document.getElementById('fullscreen-btn');
|
||||
if (fullscreenBtn) {
|
||||
const icon = fullscreenBtn.querySelector('i');
|
||||
if (icon) {
|
||||
icon.className = 'fa-solid fa-compress';
|
||||
}
|
||||
fullscreenBtn.setAttribute('title', 'Vollbildmodus beenden');
|
||||
}
|
||||
} else {
|
||||
// Vollbildmodus beenden
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen();
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen();
|
||||
} else if (document.webkitExitFullscreen) {
|
||||
document.webkitExitFullscreen();
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen();
|
||||
}
|
||||
|
||||
// Icon zurücksetzen
|
||||
const fullscreenBtn = document.getElementById('fullscreen-btn');
|
||||
if (fullscreenBtn) {
|
||||
const icon = fullscreenBtn.querySelector('i');
|
||||
if (icon) {
|
||||
icon.className = 'fa-solid fa-expand';
|
||||
}
|
||||
fullscreenBtn.setAttribute('title', 'Vollbildmodus');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisiert Effekte für Seitenleisten-Panels
|
||||
function initializePanelEffects() {
|
||||
// Selektiert alle Panel-Elemente
|
||||
const panels = document.querySelectorAll('[data-sidebar]');
|
||||
|
||||
panels.forEach(panel => {
|
||||
// Hover-Effekt für Panels
|
||||
panel.addEventListener('mouseenter', function() {
|
||||
this.classList.add('hover');
|
||||
});
|
||||
|
||||
panel.addEventListener('mouseleave', function() {
|
||||
this.classList.remove('hover');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Richtet Tastaturkürzel für Mindmap-Interaktionen ein
|
||||
function setupKeyboardShortcuts(mindmap) {
|
||||
document.addEventListener('keydown', function(e) {
|
||||
// Nur fortfahren, wenn keine Texteingabe im Fokus ist
|
||||
if (document.activeElement.tagName === 'INPUT' ||
|
||||
document.activeElement.tagName === 'TEXTAREA' ||
|
||||
document.activeElement.isContentEditable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Tastaturkürzel
|
||||
switch(e.key) {
|
||||
case '+':
|
||||
case '=':
|
||||
// Einzoomen
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
mindmap.svg.transition()
|
||||
.duration(300)
|
||||
.call(mindmap.svg.zoom().scaleBy, 1.2);
|
||||
}
|
||||
break;
|
||||
|
||||
case '-':
|
||||
// Auszoomen
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
mindmap.svg.transition()
|
||||
.duration(300)
|
||||
.call(mindmap.svg.zoom().scaleBy, 0.8);
|
||||
}
|
||||
break;
|
||||
|
||||
case '0':
|
||||
// Zoom zurücksetzen
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
mindmap.svg.transition()
|
||||
.duration(500)
|
||||
.call(mindmap.svg.zoom().transform, d3.zoomIdentity);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
// Vollbildmodus umschalten
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
e.preventDefault();
|
||||
toggleFullscreen();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Escape':
|
||||
// Ausgewählten Knoten abwählen
|
||||
if (mindmap.selectedNode) {
|
||||
mindmap.nodeClicked(null, mindmap.selectedNode);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Richtet Touch-Gesten für mobile Geräte ein
|
||||
function setupTouchInteractions(mindmap) {
|
||||
const cyContainer = document.getElementById('cy');
|
||||
if (!cyContainer) return;
|
||||
|
||||
let touchStartX, touchStartY;
|
||||
let touchStartTime;
|
||||
|
||||
// Touch-Start-Event
|
||||
cyContainer.addEventListener('touchstart', function(e) {
|
||||
if (e.touches.length === 1) {
|
||||
touchStartX = e.touches[0].clientX;
|
||||
touchStartY = e.touches[0].clientY;
|
||||
touchStartTime = Date.now();
|
||||
}
|
||||
});
|
||||
|
||||
// Touch-End-Event für Doppeltipp-Erkennung
|
||||
cyContainer.addEventListener('touchend', function(e) {
|
||||
if (Date.now() - touchStartTime < 300) { // Kurzer Tipp
|
||||
const doubleTapDelay = 300;
|
||||
const now = Date.now();
|
||||
|
||||
if (now - (window.lastTapTime || 0) < doubleTapDelay) {
|
||||
// Doppeltipp erkannt - Zentriere Ansicht
|
||||
mindmap.svg.transition()
|
||||
.duration(500)
|
||||
.call(mindmap.svg.zoom().transform, d3.zoomIdentity);
|
||||
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
window.lastTapTime = now;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Richtet einen Event-Listener für die Knotenauswahl ein
|
||||
function setupNodeSelectionListener() {
|
||||
document.addEventListener('mindmap-node-selected', function(event) {
|
||||
const node = event.detail;
|
||||
if (node) {
|
||||
console.log('Knoten ausgewählt:', node);
|
||||
showNodeDescriptionSidebar(node);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mindmap-node-deselected', function() {
|
||||
console.log('Knoten abgewählt');
|
||||
showDefaultSidebar();
|
||||
});
|
||||
}
|
||||
|
||||
// Zeigt die Standard-Seitenleiste an
|
||||
function showDefaultSidebar() {
|
||||
// Finde die Seitenleistenelemente
|
||||
const aboutMindmapPanel = document.querySelector('[data-sidebar="about-mindmap"]');
|
||||
const categoriesPanel = document.querySelector('[data-sidebar="categories"]');
|
||||
const nodeDescriptionPanel = document.querySelector('[data-sidebar="node-description"]');
|
||||
|
||||
if (aboutMindmapPanel && categoriesPanel && nodeDescriptionPanel) {
|
||||
// Beschreibungspanel ausblenden
|
||||
nodeDescriptionPanel.style.display = 'none';
|
||||
|
||||
// Standardpanels einblenden mit Animation
|
||||
aboutMindmapPanel.style.display = 'block';
|
||||
categoriesPanel.style.display = 'block';
|
||||
|
||||
setTimeout(() => {
|
||||
aboutMindmapPanel.style.opacity = '1';
|
||||
aboutMindmapPanel.style.transform = 'translateY(0)';
|
||||
|
||||
categoriesPanel.style.opacity = '1';
|
||||
categoriesPanel.style.transform = 'translateY(0)';
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
// Zeigt die Knotenbeschreibung in der Seitenleiste an
|
||||
function showNodeDescriptionSidebar(node) {
|
||||
// Finde die Seitenleistenelemente
|
||||
const aboutMindmapPanel = document.querySelector('[data-sidebar="about-mindmap"]');
|
||||
const categoriesPanel = document.querySelector('[data-sidebar="categories"]');
|
||||
const nodeDescriptionPanel = document.querySelector('[data-sidebar="node-description"]');
|
||||
|
||||
if (aboutMindmapPanel && categoriesPanel && nodeDescriptionPanel) {
|
||||
// Standardpanels ausblenden
|
||||
aboutMindmapPanel.style.transition = 'all 0.3s ease';
|
||||
categoriesPanel.style.transition = 'all 0.3s ease';
|
||||
|
||||
aboutMindmapPanel.style.opacity = '0';
|
||||
aboutMindmapPanel.style.transform = 'translateY(10px)';
|
||||
|
||||
categoriesPanel.style.opacity = '0';
|
||||
categoriesPanel.style.transform = 'translateY(10px)';
|
||||
|
||||
setTimeout(() => {
|
||||
aboutMindmapPanel.style.display = 'none';
|
||||
categoriesPanel.style.display = 'none';
|
||||
|
||||
// Beschreibungspanel vorbereiten
|
||||
const titleElement = nodeDescriptionPanel.querySelector('[data-node-title]');
|
||||
const descriptionElement = nodeDescriptionPanel.querySelector('[data-node-description]');
|
||||
|
||||
if (titleElement && descriptionElement) {
|
||||
titleElement.textContent = node.name || 'Unbenannter Knoten';
|
||||
|
||||
// Beschreibung setzen oder Standardbeschreibung generieren
|
||||
let description = node.description;
|
||||
if (!description || description.trim() === '') {
|
||||
description = generateNodeDescription(node);
|
||||
}
|
||||
|
||||
descriptionElement.textContent = description;
|
||||
}
|
||||
|
||||
// Beschreibungspanel einblenden mit Animation
|
||||
nodeDescriptionPanel.style.display = 'block';
|
||||
nodeDescriptionPanel.style.opacity = '0';
|
||||
nodeDescriptionPanel.style.transform = 'translateY(10px)';
|
||||
|
||||
setTimeout(() => {
|
||||
nodeDescriptionPanel.style.transition = 'all 0.4s ease';
|
||||
nodeDescriptionPanel.style.opacity = '1';
|
||||
nodeDescriptionPanel.style.transform = 'translateY(0)';
|
||||
}, 50);
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
// Generiert automatisch eine Beschreibung für einen Knoten ohne Beschreibung
|
||||
function generateNodeDescription(node) {
|
||||
const descriptions = {
|
||||
"Wissen": "Der zentrale Knotenpunkt der Mindmap, der alle wissenschaftlichen Disziplinen und Wissensgebiete verbindet. Hier finden sich grundlegende Erkenntnisse und Verbindungen zu spezifischeren Fachgebieten.",
|
||||
|
||||
"Quantenphysik": "Ein Zweig der Physik, der sich mit dem Verhalten und den Interaktionen von Materie und Energie auf der kleinsten Skala beschäftigt. Quantenmechanische Phänomene wie Superposition und Verschränkung bilden die Grundlage für moderne Technologien wie Quantencomputer und -kommunikation.",
|
||||
|
||||
"Neurowissenschaften": "Interdisziplinäres Forschungsgebiet, das die Struktur, Funktion und Entwicklung des Nervensystems und des Gehirns untersucht. Die Erkenntnisse beeinflussen unser Verständnis von Bewusstsein, Kognition, Verhalten und neurologischen Erkrankungen.",
|
||||
|
||||
"Künstliche Intelligenz": "Forschungsgebiet der Informatik, das sich mit der Entwicklung von Systemen befasst, die menschliche Intelligenzformen simulieren können. KI umfasst maschinelles Lernen, neuronale Netze und verschiedene Ansätze zur Problemlösung und Mustererkennung.",
|
||||
|
||||
"Klimaforschung": "Wissenschaftliche Disziplin, die sich mit der Untersuchung des Erdklimas, seinen Veränderungen und den zugrundeliegenden physikalischen Prozessen beschäftigt. Sie liefert wichtige Erkenntnisse zu Klimawandel, Wettermuster und globalen Umweltveränderungen.",
|
||||
|
||||
"Genetik": "Wissenschaft der Gene, Vererbung und der Variation von Organismen. Moderne genetische Forschung umfasst Genomik, Gentechnologie und das Verständnis der molekularen Grundlagen des Lebens sowie ihrer Anwendungen in Medizin und Biotechnologie.",
|
||||
|
||||
"Astrophysik": "Zweig der Astronomie, der die physikalischen Eigenschaften und Prozesse von Himmelskörpern und des Universums untersucht. Sie erforscht Phänomene wie Schwarze Löcher, Galaxien, kosmische Strahlung und die Entstehung und Entwicklung des Universums.",
|
||||
|
||||
"Philosophie": "Disziplin, die sich mit fundamentalen Fragen des Wissens, der Realität und der Existenz auseinandersetzt. Sie umfasst Bereiche wie Metaphysik, Erkenntnistheorie, Ethik und Logik und bildet die Grundlage für kritisches Denken und wissenschaftliche Methodik.",
|
||||
|
||||
"Wissenschaft": "Systematische Erforschung der Natur und der materiellen Welt durch Beobachtung, Experimente und die Formulierung überprüfbarer Theorien. Sie umfasst Naturwissenschaften, Sozialwissenschaften und angewandte Wissenschaften.",
|
||||
|
||||
"Technologie": "Anwendung wissenschaftlicher Erkenntnisse für praktische Zwecke. Sie umfasst die Entwicklung von Werkzeugen, Maschinen, Materialien und Prozessen zur Lösung von Problemen und zur Verbesserung der menschlichen Lebensbedingungen.",
|
||||
|
||||
"Künste": "Ausdruck menschlicher Kreativität und Imagination in verschiedenen Formen wie Malerei, Musik, Literatur, Theater und Film. Die Künste erforschen ästhetische, emotionale und intellektuelle Dimensionen der menschlichen Erfahrung.",
|
||||
|
||||
"Biologie": "Wissenschaft des Lebens und der lebenden Organismen. Sie umfasst Bereiche wie Molekularbiologie, Evolutionsbiologie, Ökologie und beschäftigt sich mit der Struktur, Funktion, Entwicklung und Evolution lebender Systeme.",
|
||||
|
||||
"Mathematik": "Wissenschaft der Muster, Strukturen und Beziehungen. Sie ist die Sprache der Naturwissenschaften und bildet die Grundlage für quantitative Analysen, logisches Denken und Problemlösung in allen wissenschaftlichen Disziplinen.",
|
||||
|
||||
"Psychologie": "Wissenschaftliche Untersuchung des menschlichen Verhaltens und der mentalen Prozesse. Sie erforscht Bereiche wie Kognition, Emotion, Persönlichkeit, soziale Interaktionen und die Behandlung psychischer Störungen.",
|
||||
|
||||
"Ethik": "Teilgebiet der Philosophie, das sich mit moralischen Prinzipien, Werten und der Frage nach richtigem und falschem Handeln beschäftigt. Sie bildet die Grundlage für moralische Entscheidungsfindung in allen Lebensbereichen."
|
||||
};
|
||||
|
||||
// Verwende vordefinierte Beschreibung, wenn verfügbar
|
||||
if (node.name && descriptions[node.name]) {
|
||||
return descriptions[node.name];
|
||||
}
|
||||
|
||||
// Generische Beschreibung basierend auf dem Knotentyp
|
||||
switch (node.type) {
|
||||
case 'category':
|
||||
return `Dieser Knoten repräsentiert die Kategorie "${node.name}", die verschiedene verwandte Konzepte und Ideen zusammenfasst. Wählen Sie einen der verbundenen Unterthemen, um mehr Details zu erfahren.`;
|
||||
case 'subcategory':
|
||||
return `"${node.name}" ist eine Unterkategorie, die spezifische Aspekte eines größeren Themenbereichs beleuchtet. Die verbundenen Knoten zeigen wichtige Konzepte und Ideen innerhalb dieses Bereichs.`;
|
||||
default:
|
||||
return `Dieser Knoten repräsentiert das Konzept "${node.name}". Erforschen Sie die verbundenen Knoten, um Zusammenhänge und verwandte Ideen zu entdecken.`;
|
||||
}
|
||||
}
|
||||
@@ -1,234 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Interaktive Mindmap</title>
|
||||
|
||||
<!-- Cytoscape.js -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
|
||||
|
||||
<!-- Socket.IO -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
|
||||
|
||||
<!-- Feather Icons (optional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f9fafb;
|
||||
color: #111827;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #1f2937;
|
||||
color: white;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
background-color: #f3f4f6;
|
||||
padding: 0.75rem;
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: #3b82f6;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #2563eb;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6b7280;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #4b5563;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background-color: #dc2626;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
#cy {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.category-filters {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.75rem;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.category-filter {
|
||||
border: none;
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.category-filter:not(.active) {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.category-filter:hover:not(.active) {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.footer {
|
||||
background-color: #f3f4f6;
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
color: #6b7280;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* Kontextmenü Styling */
|
||||
#context-menu {
|
||||
position: absolute;
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#context-menu .menu-item {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#context-menu .menu-item:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header class="header">
|
||||
<h1>Interaktive Mindmap</h1>
|
||||
<div class="search-container">
|
||||
<input type="text" id="search-mindmap" class="search-input" placeholder="Suchen...">
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="toolbar">
|
||||
<button id="addNode" class="btn">
|
||||
<i data-feather="plus-circle"></i>
|
||||
Knoten hinzufügen
|
||||
</button>
|
||||
<button id="addEdge" class="btn">
|
||||
<i data-feather="git-branch"></i>
|
||||
Verbindung erstellen
|
||||
</button>
|
||||
<button id="editNode" class="btn btn-secondary">
|
||||
<i data-feather="edit-2"></i>
|
||||
Knoten bearbeiten
|
||||
</button>
|
||||
<button id="deleteNode" class="btn btn-danger">
|
||||
<i data-feather="trash-2"></i>
|
||||
Knoten löschen
|
||||
</button>
|
||||
<button id="deleteEdge" class="btn btn-danger">
|
||||
<i data-feather="scissors"></i>
|
||||
Verbindung löschen
|
||||
</button>
|
||||
<button id="reLayout" class="btn btn-secondary">
|
||||
<i data-feather="refresh-cw"></i>
|
||||
Layout neu anordnen
|
||||
</button>
|
||||
<button id="exportMindmap" class="btn btn-secondary">
|
||||
<i data-feather="download"></i>
|
||||
Exportieren
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="category-filters" class="category-filters">
|
||||
<!-- Wird dynamisch befüllt -->
|
||||
</div>
|
||||
|
||||
<div id="cy"></div>
|
||||
|
||||
<footer class="footer">
|
||||
Mindmap-Anwendung © 2023
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<!-- Unsere Mindmap JS -->
|
||||
<script src="../js/mindmap.js"></script>
|
||||
|
||||
<!-- Icons initialisieren -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (typeof feather !== 'undefined') {
|
||||
feather.replace();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,749 +0,0 @@
|
||||
/**
|
||||
* Mindmap.js - Interaktive Mind-Map Implementierung
|
||||
* - Cytoscape.js für Graph-Rendering
|
||||
* - Fetch API für REST-Zugriffe
|
||||
* - Socket.IO für Echtzeit-Synchronisation
|
||||
*/
|
||||
|
||||
(async () => {
|
||||
/* 1. Initialisierung und Grundkonfiguration */
|
||||
const cy = cytoscape({
|
||||
container: document.getElementById('cy'),
|
||||
style: [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'label': 'data(name)',
|
||||
'text-valign': 'center',
|
||||
'color': '#fff',
|
||||
'background-color': 'data(color)',
|
||||
'width': 45,
|
||||
'height': 45,
|
||||
'font-size': 11,
|
||||
'text-outline-width': 1,
|
||||
'text-outline-color': '#000',
|
||||
'text-outline-opacity': 0.5,
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': 80
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'node[icon]',
|
||||
style: {
|
||||
'background-image': function(ele) {
|
||||
return `static/img/icons/${ele.data('icon')}.svg`;
|
||||
},
|
||||
'background-width': '60%',
|
||||
'background-height': '60%',
|
||||
'background-position-x': '50%',
|
||||
'background-position-y': '40%',
|
||||
'text-margin-y': 10
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': 2,
|
||||
'line-color': '#888',
|
||||
'target-arrow-shape': 'triangle',
|
||||
'curve-style': 'bezier',
|
||||
'target-arrow-color': '#888'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: ':selected',
|
||||
style: {
|
||||
'border-width': 3,
|
||||
'border-color': '#f8f32b'
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: {
|
||||
name: 'breadthfirst',
|
||||
directed: true,
|
||||
padding: 30,
|
||||
spacingFactor: 1.2
|
||||
}
|
||||
});
|
||||
|
||||
/* 2. Hilfs-Funktionen für API-Zugriffe */
|
||||
const get = async endpoint => {
|
||||
try {
|
||||
const response = await fetch(endpoint);
|
||||
if (!response.ok) {
|
||||
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
|
||||
return []; // Leeres Array zurückgeben bei Fehlern
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Fehler beim Abrufen von ${endpoint}:`, error);
|
||||
return []; // Leeres Array zurückgeben bei Netzwerkfehlern
|
||||
}
|
||||
};
|
||||
|
||||
const post = async (endpoint, body) => {
|
||||
try {
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
|
||||
return {}; // Leeres Objekt zurückgeben bei Fehlern
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Fehler beim POST zu ${endpoint}:`, error);
|
||||
return {}; // Leeres Objekt zurückgeben bei Netzwerkfehlern
|
||||
}
|
||||
};
|
||||
|
||||
const del = async endpoint => {
|
||||
try {
|
||||
const response = await fetch(endpoint, { method: 'DELETE' });
|
||||
if (!response.ok) {
|
||||
console.error(`API-Fehler: ${endpoint} antwortet mit Status ${response.status}`);
|
||||
return {}; // Leeres Objekt zurückgeben bei Fehlern
|
||||
}
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error(`Fehler beim DELETE zu ${endpoint}:`, error);
|
||||
return {}; // Leeres Objekt zurückgeben bei Netzwerkfehlern
|
||||
}
|
||||
};
|
||||
|
||||
/* 3. Kategorien laden für Style-Informationen */
|
||||
let categories = await get('/api/categories');
|
||||
|
||||
/* 4. Daten laden und Rendering */
|
||||
const loadMindmap = async () => {
|
||||
try {
|
||||
// Nodes und Beziehungen parallel laden
|
||||
const [nodes, relationships] = await Promise.all([
|
||||
get('/api/mind_map_nodes'),
|
||||
get('/api/node_relationships')
|
||||
]);
|
||||
|
||||
// Graph leeren (für Reload-Fälle)
|
||||
cy.elements().remove();
|
||||
|
||||
// Überprüfen, ob nodes ein Array ist, wenn nicht, setze es auf ein leeres Array
|
||||
const nodesArray = Array.isArray(nodes) ? nodes : [];
|
||||
|
||||
// Knoten zum Graph hinzufügen
|
||||
cy.add(
|
||||
nodesArray.map(node => {
|
||||
// Kategorie-Informationen für Styling abrufen
|
||||
const category = categories.find(c => c.id === node.category_id) || {};
|
||||
|
||||
return {
|
||||
data: {
|
||||
id: node.id.toString(),
|
||||
name: node.name,
|
||||
description: node.description,
|
||||
color: node.color_code || category.color_code || '#6b7280',
|
||||
icon: node.icon || category.icon,
|
||||
category_id: node.category_id
|
||||
},
|
||||
position: node.x && node.y ? { x: node.x, y: node.y } : undefined
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Überprüfen, ob relationships ein Array ist, wenn nicht, setze es auf ein leeres Array
|
||||
const relationshipsArray = Array.isArray(relationships) ? relationships : [];
|
||||
|
||||
// Kanten zum Graph hinzufügen
|
||||
cy.add(
|
||||
relationshipsArray.map(rel => ({
|
||||
data: {
|
||||
id: `${rel.parent_id}_${rel.child_id}`,
|
||||
source: rel.parent_id.toString(),
|
||||
target: rel.child_id.toString()
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
||||
// Wenn keine Knoten geladen wurden, Fallback-Knoten erstellen
|
||||
if (nodesArray.length === 0) {
|
||||
// Mindestens einen Standardknoten hinzufügen
|
||||
cy.add({
|
||||
data: {
|
||||
id: 'fallback-1',
|
||||
name: 'Mindmap',
|
||||
description: 'Erstellen Sie hier Ihre eigene Mindmap',
|
||||
color: '#3b82f6',
|
||||
icon: 'help-circle'
|
||||
},
|
||||
position: { x: 300, y: 200 }
|
||||
});
|
||||
|
||||
// Erfolgsmeldung anzeigen
|
||||
console.log('Mindmap erfolgreich initialisiert mit Fallback-Knoten');
|
||||
|
||||
// Info-Meldung für Benutzer anzeigen
|
||||
const infoBox = document.createElement('div');
|
||||
infoBox.classList.add('info-message');
|
||||
infoBox.style.position = 'absolute';
|
||||
infoBox.style.top = '50%';
|
||||
infoBox.style.left = '50%';
|
||||
infoBox.style.transform = 'translate(-50%, -50%)';
|
||||
infoBox.style.padding = '15px 20px';
|
||||
infoBox.style.backgroundColor = 'rgba(59, 130, 246, 0.9)';
|
||||
infoBox.style.color = 'white';
|
||||
infoBox.style.borderRadius = '8px';
|
||||
infoBox.style.zIndex = '5';
|
||||
infoBox.style.maxWidth = '80%';
|
||||
infoBox.style.textAlign = 'center';
|
||||
infoBox.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
|
||||
infoBox.innerHTML = 'Mindmap erfolgreich initialisiert.<br>Verwenden Sie die Werkzeugleiste, um Knoten hinzuzufügen.';
|
||||
|
||||
document.getElementById('cy').appendChild(infoBox);
|
||||
|
||||
// Meldung nach 5 Sekunden ausblenden
|
||||
setTimeout(() => {
|
||||
infoBox.style.opacity = '0';
|
||||
infoBox.style.transition = 'opacity 0.5s ease';
|
||||
setTimeout(() => {
|
||||
if (infoBox.parentNode) {
|
||||
infoBox.parentNode.removeChild(infoBox);
|
||||
}
|
||||
}, 500);
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Layout anwenden wenn keine Positionsdaten vorhanden
|
||||
const nodesWithoutPosition = cy.nodes().filter(node =>
|
||||
!node.position() || (node.position().x === 0 && node.position().y === 0)
|
||||
);
|
||||
|
||||
if (nodesWithoutPosition.length > 0) {
|
||||
cy.layout({
|
||||
name: 'breadthfirst',
|
||||
directed: true,
|
||||
padding: 30,
|
||||
spacingFactor: 1.2
|
||||
}).run();
|
||||
}
|
||||
|
||||
// Tooltip-Funktionalität
|
||||
cy.nodes().unbind('mouseover').bind('mouseover', (event) => {
|
||||
const node = event.target;
|
||||
const description = node.data('description');
|
||||
|
||||
if (description) {
|
||||
const tooltip = document.getElementById('node-tooltip') ||
|
||||
document.createElement('div');
|
||||
|
||||
if (!tooltip.id) {
|
||||
tooltip.id = 'node-tooltip';
|
||||
tooltip.style.position = 'absolute';
|
||||
tooltip.style.backgroundColor = '#333';
|
||||
tooltip.style.color = '#fff';
|
||||
tooltip.style.padding = '8px';
|
||||
tooltip.style.borderRadius = '4px';
|
||||
tooltip.style.maxWidth = '250px';
|
||||
tooltip.style.zIndex = 10;
|
||||
tooltip.style.pointerEvents = 'none';
|
||||
tooltip.style.transition = 'opacity 0.2s';
|
||||
tooltip.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
|
||||
document.body.appendChild(tooltip);
|
||||
}
|
||||
|
||||
const renderedPosition = node.renderedPosition();
|
||||
const containerRect = cy.container().getBoundingClientRect();
|
||||
|
||||
tooltip.innerHTML = description;
|
||||
tooltip.style.left = (containerRect.left + renderedPosition.x + 25) + 'px';
|
||||
tooltip.style.top = (containerRect.top + renderedPosition.y - 15) + 'px';
|
||||
tooltip.style.opacity = '1';
|
||||
}
|
||||
});
|
||||
|
||||
cy.nodes().unbind('mouseout').bind('mouseout', () => {
|
||||
const tooltip = document.getElementById('node-tooltip');
|
||||
if (tooltip) {
|
||||
tooltip.style.opacity = '0';
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Mindmap:', error);
|
||||
alert('Die Mindmap konnte nicht geladen werden. Bitte prüfen Sie die Konsole für Details.');
|
||||
}
|
||||
};
|
||||
|
||||
// Initial laden
|
||||
await loadMindmap();
|
||||
|
||||
/* 5. Socket.IO für Echtzeit-Synchronisation */
|
||||
const socket = io();
|
||||
|
||||
socket.on('node_added', async (node) => {
|
||||
// Kategorie-Informationen für Styling abrufen
|
||||
const category = categories.find(c => c.id === node.category_id) || {};
|
||||
|
||||
cy.add({
|
||||
data: {
|
||||
id: node.id.toString(),
|
||||
name: node.name,
|
||||
description: node.description,
|
||||
color: node.color_code || category.color_code || '#6b7280',
|
||||
icon: node.icon || category.icon,
|
||||
category_id: node.category_id
|
||||
}
|
||||
});
|
||||
|
||||
// Layout neu anwenden, wenn nötig
|
||||
if (!node.x || !node.y) {
|
||||
cy.layout({ name: 'breadthfirst', directed: true, padding: 30 }).run();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('node_updated', (node) => {
|
||||
const cyNode = cy.$id(node.id.toString());
|
||||
if (cyNode.length > 0) {
|
||||
// Kategorie-Informationen für Styling abrufen
|
||||
const category = categories.find(c => c.id === node.category_id) || {};
|
||||
|
||||
cyNode.data({
|
||||
name: node.name,
|
||||
description: node.description,
|
||||
color: node.color_code || category.color_code || '#6b7280',
|
||||
icon: node.icon || category.icon,
|
||||
category_id: node.category_id
|
||||
});
|
||||
|
||||
if (node.x && node.y) {
|
||||
cyNode.position({ x: node.x, y: node.y });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('node_deleted', (nodeId) => {
|
||||
const cyNode = cy.$id(nodeId.toString());
|
||||
if (cyNode.length > 0) {
|
||||
cy.remove(cyNode);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('relationship_added', (rel) => {
|
||||
cy.add({
|
||||
data: {
|
||||
id: `${rel.parent_id}_${rel.child_id}`,
|
||||
source: rel.parent_id.toString(),
|
||||
target: rel.child_id.toString()
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('relationship_deleted', (rel) => {
|
||||
const edgeId = `${rel.parent_id}_${rel.child_id}`;
|
||||
const cyEdge = cy.$id(edgeId);
|
||||
if (cyEdge.length > 0) {
|
||||
cy.remove(cyEdge);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('category_updated', async () => {
|
||||
// Kategorien neu laden
|
||||
categories = await get('/api/categories');
|
||||
// Nodes aktualisieren, die diese Kategorie verwenden
|
||||
cy.nodes().forEach(node => {
|
||||
const categoryId = node.data('category_id');
|
||||
if (categoryId) {
|
||||
const category = categories.find(c => c.id === categoryId);
|
||||
if (category) {
|
||||
node.data('color', node.data('color_code') || category.color_code);
|
||||
node.data('icon', node.data('icon') || category.icon);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* 6. UI-Interaktionen */
|
||||
// Knoten hinzufügen
|
||||
const btnAddNode = document.getElementById('addNode');
|
||||
if (btnAddNode) {
|
||||
btnAddNode.addEventListener('click', async () => {
|
||||
const name = prompt('Knotenname eingeben:');
|
||||
if (!name) return;
|
||||
|
||||
const description = prompt('Beschreibung (optional):');
|
||||
|
||||
// Kategorie auswählen
|
||||
let categoryId = null;
|
||||
if (categories.length > 0) {
|
||||
const categoryOptions = categories.map((c, i) => `${i}: ${c.name}`).join('\n');
|
||||
const categoryChoice = prompt(
|
||||
`Kategorie auswählen (Nummer eingeben):\n${categoryOptions}`,
|
||||
'0'
|
||||
);
|
||||
|
||||
if (categoryChoice !== null) {
|
||||
const index = parseInt(categoryChoice, 10);
|
||||
if (!isNaN(index) && index >= 0 && index < categories.length) {
|
||||
categoryId = categories[index].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Knoten erstellen
|
||||
await post('/api/mind_map_node', {
|
||||
name,
|
||||
description,
|
||||
category_id: categoryId
|
||||
});
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
});
|
||||
}
|
||||
|
||||
// Verbindung hinzufügen
|
||||
const btnAddEdge = document.getElementById('addEdge');
|
||||
if (btnAddEdge) {
|
||||
btnAddEdge.addEventListener('click', async () => {
|
||||
const sel = cy.$('node:selected');
|
||||
if (sel.length !== 2) {
|
||||
alert('Bitte genau zwei Knoten auswählen (Parent → Child)');
|
||||
return;
|
||||
}
|
||||
|
||||
const [parent, child] = sel.map(node => node.id());
|
||||
await post('/api/node_relationship', {
|
||||
parent_id: parent,
|
||||
child_id: child
|
||||
});
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
});
|
||||
}
|
||||
|
||||
// Knoten bearbeiten
|
||||
const btnEditNode = document.getElementById('editNode');
|
||||
if (btnEditNode) {
|
||||
btnEditNode.addEventListener('click', async () => {
|
||||
const sel = cy.$('node:selected');
|
||||
if (sel.length !== 1) {
|
||||
alert('Bitte genau einen Knoten auswählen');
|
||||
return;
|
||||
}
|
||||
|
||||
const node = sel[0];
|
||||
const nodeData = node.data();
|
||||
|
||||
const name = prompt('Knotenname:', nodeData.name);
|
||||
if (!name) return;
|
||||
|
||||
const description = prompt('Beschreibung:', nodeData.description || '');
|
||||
|
||||
// Kategorie auswählen
|
||||
let categoryId = nodeData.category_id;
|
||||
if (categories.length > 0) {
|
||||
const categoryOptions = categories.map((c, i) => `${i}: ${c.name}`).join('\n');
|
||||
const categoryChoice = prompt(
|
||||
`Kategorie auswählen (Nummer eingeben):\n${categoryOptions}`,
|
||||
categories.findIndex(c => c.id === categoryId).toString()
|
||||
);
|
||||
|
||||
if (categoryChoice !== null) {
|
||||
const index = parseInt(categoryChoice, 10);
|
||||
if (!isNaN(index) && index >= 0 && index < categories.length) {
|
||||
categoryId = categories[index].id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Knoten aktualisieren
|
||||
await post(`/api/mind_map_node/${nodeData.id}`, {
|
||||
name,
|
||||
description,
|
||||
category_id: categoryId
|
||||
});
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
});
|
||||
}
|
||||
|
||||
// Knoten löschen
|
||||
const btnDeleteNode = document.getElementById('deleteNode');
|
||||
if (btnDeleteNode) {
|
||||
btnDeleteNode.addEventListener('click', async () => {
|
||||
const sel = cy.$('node:selected');
|
||||
if (sel.length !== 1) {
|
||||
alert('Bitte genau einen Knoten auswählen');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('Sind Sie sicher, dass Sie diesen Knoten löschen möchten?')) {
|
||||
const nodeId = sel[0].id();
|
||||
await del(`/api/mind_map_node/${nodeId}`);
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Verbindung löschen
|
||||
const btnDeleteEdge = document.getElementById('deleteEdge');
|
||||
if (btnDeleteEdge) {
|
||||
btnDeleteEdge.addEventListener('click', async () => {
|
||||
const sel = cy.$('edge:selected');
|
||||
if (sel.length !== 1) {
|
||||
alert('Bitte genau eine Verbindung auswählen');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm('Sind Sie sicher, dass Sie diese Verbindung löschen möchten?')) {
|
||||
const edge = sel[0];
|
||||
const parentId = edge.source().id();
|
||||
const childId = edge.target().id();
|
||||
|
||||
await del(`/api/node_relationship/${parentId}/${childId}`);
|
||||
// Darstellung wird durch Socket.IO Event übernommen
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Layout aktualisieren
|
||||
const btnReLayout = document.getElementById('reLayout');
|
||||
if (btnReLayout) {
|
||||
btnReLayout.addEventListener('click', () => {
|
||||
cy.layout({
|
||||
name: 'breadthfirst',
|
||||
directed: true,
|
||||
padding: 30,
|
||||
spacingFactor: 1.2
|
||||
}).run();
|
||||
});
|
||||
}
|
||||
|
||||
/* 7. Position speichern bei Drag & Drop */
|
||||
cy.on('dragfree', 'node', async (e) => {
|
||||
const node = e.target;
|
||||
const position = node.position();
|
||||
|
||||
await post(`/api/mind_map_node/${node.id()}/position`, {
|
||||
x: Math.round(position.x),
|
||||
y: Math.round(position.y)
|
||||
});
|
||||
|
||||
// Andere Benutzer erhalten die Position über den node_updated Event
|
||||
});
|
||||
|
||||
/* 8. Kontextmenü (optional) */
|
||||
const setupContextMenu = () => {
|
||||
cy.on('cxttap', 'node', function(e) {
|
||||
const node = e.target;
|
||||
const nodeData = node.data();
|
||||
|
||||
// Position des Kontextmenüs berechnen
|
||||
const renderedPosition = node.renderedPosition();
|
||||
const containerRect = cy.container().getBoundingClientRect();
|
||||
const menuX = containerRect.left + renderedPosition.x;
|
||||
const menuY = containerRect.top + renderedPosition.y;
|
||||
|
||||
// Kontextmenü erstellen oder aktualisieren
|
||||
let contextMenu = document.getElementById('context-menu');
|
||||
if (!contextMenu) {
|
||||
contextMenu = document.createElement('div');
|
||||
contextMenu.id = 'context-menu';
|
||||
contextMenu.style.position = 'absolute';
|
||||
contextMenu.style.backgroundColor = '#fff';
|
||||
contextMenu.style.border = '1px solid #ccc';
|
||||
contextMenu.style.borderRadius = '4px';
|
||||
contextMenu.style.padding = '5px 0';
|
||||
contextMenu.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
|
||||
contextMenu.style.zIndex = 1000;
|
||||
document.body.appendChild(contextMenu);
|
||||
}
|
||||
|
||||
// Menüinhalte
|
||||
contextMenu.innerHTML = `
|
||||
<div class="menu-item" data-action="edit">Knoten bearbeiten</div>
|
||||
<div class="menu-item" data-action="connect">Verbindung erstellen</div>
|
||||
<div class="menu-item" data-action="delete">Knoten löschen</div>
|
||||
`;
|
||||
|
||||
// Styling für Menüpunkte
|
||||
const menuItems = contextMenu.querySelectorAll('.menu-item');
|
||||
menuItems.forEach(item => {
|
||||
item.style.padding = '8px 20px';
|
||||
item.style.cursor = 'pointer';
|
||||
item.style.fontSize = '14px';
|
||||
|
||||
item.addEventListener('mouseover', function() {
|
||||
this.style.backgroundColor = '#f0f0f0';
|
||||
});
|
||||
|
||||
item.addEventListener('mouseout', function() {
|
||||
this.style.backgroundColor = 'transparent';
|
||||
});
|
||||
|
||||
// Event-Handler
|
||||
item.addEventListener('click', async function() {
|
||||
const action = this.getAttribute('data-action');
|
||||
|
||||
switch(action) {
|
||||
case 'edit':
|
||||
// Knoten bearbeiten (gleiche Logik wie beim Edit-Button)
|
||||
const name = prompt('Knotenname:', nodeData.name);
|
||||
if (name) {
|
||||
const description = prompt('Beschreibung:', nodeData.description || '');
|
||||
await post(`/api/mind_map_node/${nodeData.id}`, { name, description });
|
||||
}
|
||||
break;
|
||||
|
||||
case 'connect':
|
||||
// Modus zum Verbinden aktivieren
|
||||
cy.nodes().unselect();
|
||||
node.select();
|
||||
alert('Wählen Sie nun einen zweiten Knoten aus, um eine Verbindung zu erstellen');
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if (confirm('Sind Sie sicher, dass Sie diesen Knoten löschen möchten?')) {
|
||||
await del(`/api/mind_map_node/${nodeData.id}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Menü schließen
|
||||
contextMenu.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Menü positionieren und anzeigen
|
||||
contextMenu.style.left = menuX + 'px';
|
||||
contextMenu.style.top = menuY + 'px';
|
||||
contextMenu.style.display = 'block';
|
||||
|
||||
// Event-Listener zum Schließen des Menüs
|
||||
const closeMenu = function() {
|
||||
if (contextMenu) {
|
||||
contextMenu.style.display = 'none';
|
||||
}
|
||||
document.removeEventListener('click', closeMenu);
|
||||
};
|
||||
|
||||
// Verzögerung, um den aktuellen Click nicht zu erfassen
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', closeMenu);
|
||||
}, 0);
|
||||
|
||||
e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
// Kontextmenü aktivieren (optional)
|
||||
// setupContextMenu();
|
||||
|
||||
/* 9. Export-Funktion (optional) */
|
||||
const btnExport = document.getElementById('exportMindmap');
|
||||
if (btnExport) {
|
||||
btnExport.addEventListener('click', () => {
|
||||
const elements = cy.json().elements;
|
||||
const exportData = {
|
||||
nodes: elements.nodes.map(n => ({
|
||||
id: n.data.id,
|
||||
name: n.data.name,
|
||||
description: n.data.description,
|
||||
category_id: n.data.category_id,
|
||||
x: Math.round(n.position?.x || 0),
|
||||
y: Math.round(n.position?.y || 0)
|
||||
})),
|
||||
relationships: elements.edges.map(e => ({
|
||||
parent_id: e.data.source,
|
||||
child_id: e.data.target
|
||||
}))
|
||||
};
|
||||
|
||||
const blob = new Blob([JSON.stringify(exportData, null, 2)], {type: 'application/json'});
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = 'mindmap_export.json';
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
});
|
||||
}
|
||||
|
||||
/* 10. Filter-Funktion nach Kategorien (optional) */
|
||||
const setupCategoryFilters = () => {
|
||||
const filterContainer = document.getElementById('category-filters');
|
||||
if (!filterContainer || !categories.length) return;
|
||||
|
||||
filterContainer.innerHTML = '';
|
||||
|
||||
// "Alle anzeigen" Option
|
||||
const allBtn = document.createElement('button');
|
||||
allBtn.innerText = 'Alle Kategorien';
|
||||
allBtn.className = 'category-filter active';
|
||||
allBtn.onclick = () => {
|
||||
document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
|
||||
allBtn.classList.add('active');
|
||||
cy.nodes().removeClass('filtered').show();
|
||||
cy.edges().show();
|
||||
};
|
||||
filterContainer.appendChild(allBtn);
|
||||
|
||||
// Filter-Button pro Kategorie
|
||||
categories.forEach(category => {
|
||||
const btn = document.createElement('button');
|
||||
btn.innerText = category.name;
|
||||
btn.className = 'category-filter';
|
||||
btn.style.backgroundColor = category.color_code;
|
||||
btn.style.color = '#fff';
|
||||
btn.onclick = () => {
|
||||
document.querySelectorAll('.category-filter').forEach(btn => btn.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
const matchingNodes = cy.nodes().filter(node => node.data('category_id') === category.id);
|
||||
cy.nodes().addClass('filtered').hide();
|
||||
matchingNodes.removeClass('filtered').show();
|
||||
|
||||
// Verbindungen zu/von diesen Knoten anzeigen
|
||||
cy.edges().hide();
|
||||
matchingNodes.connectedEdges().show();
|
||||
};
|
||||
filterContainer.appendChild(btn);
|
||||
});
|
||||
};
|
||||
|
||||
// Filter-Funktionalität aktivieren (optional)
|
||||
// setupCategoryFilters();
|
||||
|
||||
/* 11. Suchfunktion (optional) */
|
||||
const searchInput = document.getElementById('search-mindmap');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
|
||||
if (!searchTerm) {
|
||||
cy.nodes().removeClass('search-hidden').show();
|
||||
cy.edges().show();
|
||||
return;
|
||||
}
|
||||
|
||||
cy.nodes().forEach(node => {
|
||||
const name = node.data('name').toLowerCase();
|
||||
const description = (node.data('description') || '').toLowerCase();
|
||||
|
||||
if (name.includes(searchTerm) || description.includes(searchTerm)) {
|
||||
node.removeClass('search-hidden').show();
|
||||
node.connectedEdges().show();
|
||||
} else {
|
||||
node.addClass('search-hidden').hide();
|
||||
// Kanten nur verstecken, wenn beide verbundenen Knoten versteckt sind
|
||||
node.connectedEdges().forEach(edge => {
|
||||
const otherNode = edge.source().id() === node.id() ? edge.target() : edge.source();
|
||||
if (otherNode.hasClass('search-hidden')) {
|
||||
edge.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Mindmap erfolgreich initialisiert');
|
||||
})();
|
||||
@@ -26,105 +26,216 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
function initMindmapPage() {
|
||||
console.log('Mindmap-Seite wird initialisiert...');
|
||||
|
||||
// Hauptcontainer für die Mindmap
|
||||
const cyContainer = document.getElementById('cy');
|
||||
if (!cyContainer) {
|
||||
console.error('Mindmap-Container #cy nicht gefunden!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Info-Panel für Knotendetails
|
||||
const nodeInfoPanel = document.getElementById('node-info-panel');
|
||||
const nodeDescription = document.getElementById('node-description');
|
||||
const connectedNodes = document.getElementById('connected-nodes');
|
||||
|
||||
// Toolbar-Buttons
|
||||
const fitButton = document.getElementById('fit-btn');
|
||||
const resetButton = document.getElementById('reset-btn');
|
||||
const toggleLabelsButton = document.getElementById('toggle-labels-btn');
|
||||
|
||||
// Mindmap-Instanz
|
||||
let mindmap = null;
|
||||
|
||||
// Cytoscape.js für die Visualisierung initialisieren
|
||||
try {
|
||||
// Cytoscape.js-Bibliothek überprüfen
|
||||
if (typeof cytoscape === 'undefined') {
|
||||
loadExternalScript('https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js')
|
||||
.then(() => {
|
||||
initCytoscape();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden von Cytoscape.js:', error);
|
||||
showErrorMessage(cyContainer, 'Cytoscape.js konnte nicht geladen werden.');
|
||||
});
|
||||
} else {
|
||||
initCytoscape();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der Initialisierung der Mindmap:', error);
|
||||
showErrorMessage(cyContainer, 'Die Mindmap konnte nicht initialisiert werden: ' + error.message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt ein externes Script asynchron
|
||||
*/
|
||||
function loadExternalScript(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = url;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Fehlermeldung im Container an
|
||||
*/
|
||||
function showErrorMessage(container, message) {
|
||||
container.innerHTML = `
|
||||
<div class="p-8 text-center bg-red-50 dark:bg-red-900/20 rounded-lg">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl text-red-500 mb-4"></i>
|
||||
<p class="text-lg text-red-600 dark:text-red-400">${message}</p>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Bitte laden Sie die Seite neu oder kontaktieren Sie den Support.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert Cytoscape mit den Mindmap-Daten
|
||||
*/
|
||||
function initCytoscape() {
|
||||
console.log('Cytoscape.js wird initialisiert...');
|
||||
|
||||
// Zeige Ladeanimation
|
||||
cyContainer.innerHTML = `
|
||||
<div class="flex justify-center items-center h-full">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-purple-500"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Lade Daten vom Backend
|
||||
fetch('/api/mindmap')
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Netzwerkfehler beim Laden der Mindmap-Daten');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Mindmap-Daten erfolgreich geladen');
|
||||
renderMindmap(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Fehler beim Laden der Mindmap-Daten:', error);
|
||||
|
||||
// Verwende Standarddaten als Fallback
|
||||
console.log('Verwende Standarddaten als Fallback...');
|
||||
const defaultData = generateDefaultData();
|
||||
renderMindmap(defaultData);
|
||||
// Warte auf die Cytoscape-Instanz
|
||||
document.addEventListener('mindmap-loaded', function() {
|
||||
const cy = window.cy;
|
||||
if (!cy) return;
|
||||
|
||||
// Event-Listener für Knoten-Klicks
|
||||
cy.on('tap', 'node', function(evt) {
|
||||
const node = evt.target;
|
||||
|
||||
// Alle vorherigen Hervorhebungen zurücksetzen
|
||||
cy.nodes().forEach(n => {
|
||||
n.removeStyle();
|
||||
n.connectedEdges().removeStyle();
|
||||
});
|
||||
|
||||
// Speichere ausgewählten Knoten
|
||||
window.mindmapInstance.selectedNode = node;
|
||||
|
||||
// Aktiviere leuchtenden Effekt statt Umkreisung
|
||||
node.style({
|
||||
'background-opacity': 1,
|
||||
'background-color': node.data('color'),
|
||||
'shadow-color': node.data('color'),
|
||||
'shadow-opacity': 1,
|
||||
'shadow-blur': 15,
|
||||
'shadow-offset-x': 0,
|
||||
'shadow-offset-y': 0
|
||||
});
|
||||
|
||||
// Verbundene Kanten und Knoten hervorheben
|
||||
const connectedEdges = node.connectedEdges();
|
||||
const connectedNodes = node.neighborhood('node');
|
||||
|
||||
connectedEdges.style({
|
||||
'line-color': '#a78bfa',
|
||||
'target-arrow-color': '#a78bfa',
|
||||
'source-arrow-color': '#a78bfa',
|
||||
'line-opacity': 0.8,
|
||||
'width': 2
|
||||
});
|
||||
|
||||
connectedNodes.style({
|
||||
'shadow-opacity': 0.7,
|
||||
'shadow-blur': 10,
|
||||
'shadow-color': '#a78bfa'
|
||||
});
|
||||
|
||||
// Info-Panel aktualisieren
|
||||
updateInfoPanel(node);
|
||||
|
||||
// Seitenleiste aktualisieren
|
||||
updateSidebar(node);
|
||||
});
|
||||
|
||||
// Klick auf Hintergrund - Auswahl zurücksetzen
|
||||
cy.on('tap', function(evt) {
|
||||
if (evt.target === cy) {
|
||||
resetSelection(cy);
|
||||
}
|
||||
});
|
||||
|
||||
// Zoom-Controls
|
||||
document.getElementById('zoomIn')?.addEventListener('click', () => {
|
||||
cy.zoom({
|
||||
level: cy.zoom() * 1.2,
|
||||
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('zoomOut')?.addEventListener('click', () => {
|
||||
cy.zoom({
|
||||
level: cy.zoom() / 1.2,
|
||||
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('resetView')?.addEventListener('click', () => {
|
||||
cy.fit();
|
||||
resetSelection(cy);
|
||||
});
|
||||
|
||||
// Legend-Toggle
|
||||
document.getElementById('toggleLegend')?.addEventListener('click', () => {
|
||||
const legend = document.getElementById('categoryLegend');
|
||||
if (legend) {
|
||||
isLegendVisible = !isLegendVisible;
|
||||
legend.style.display = isLegendVisible ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard-Controls
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === '+' || e.key === '=') {
|
||||
cy.zoom({
|
||||
level: cy.zoom() * 1.2,
|
||||
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
|
||||
});
|
||||
} else if (e.key === '-' || e.key === '_') {
|
||||
cy.zoom({
|
||||
level: cy.zoom() / 1.2,
|
||||
renderedPosition: { x: cy.width() / 2, y: cy.height() / 2 }
|
||||
});
|
||||
} else if (e.key === 'Escape') {
|
||||
resetSelection(cy);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert das Info-Panel mit Knoteninformationen
|
||||
* @param {Object} node - Der ausgewählte Knoten
|
||||
*/
|
||||
function updateInfoPanel(node) {
|
||||
const infoPanel = document.getElementById('infoPanel');
|
||||
if (!infoPanel) return;
|
||||
|
||||
const data = node.data();
|
||||
const connectedNodes = node.neighborhood('node');
|
||||
|
||||
let html = `
|
||||
<h3>${data.label || data.name}</h3>
|
||||
<p class="category">${data.category || 'Keine Kategorie'}</p>
|
||||
${data.description ? `<p class="description">${data.description}</p>` : ''}
|
||||
<div class="connections">
|
||||
<h4>Verbindungen (${connectedNodes.length})</h4>
|
||||
<ul>
|
||||
`;
|
||||
|
||||
connectedNodes.forEach(connectedNode => {
|
||||
const connectedData = connectedNode.data();
|
||||
html += `
|
||||
<li style="color: ${connectedData.color || '#60a5fa'}">
|
||||
${connectedData.label || connectedData.name}
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
infoPanel.innerHTML = html;
|
||||
infoPanel.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert die Seitenleiste mit Knoteninformationen
|
||||
* @param {Object} node - Der ausgewählte Knoten
|
||||
*/
|
||||
function updateSidebar(node) {
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (!sidebar) return;
|
||||
|
||||
const data = node.data();
|
||||
const connectedNodes = node.neighborhood('node');
|
||||
|
||||
let html = `
|
||||
<div class="node-details">
|
||||
<h3>${data.label || data.name}</h3>
|
||||
<p class="category">${data.category || 'Keine Kategorie'}</p>
|
||||
${data.description ? `<p class="description">${data.description}</p>` : ''}
|
||||
<div class="connections">
|
||||
<h4>Verbindungen (${connectedNodes.length})</h4>
|
||||
<ul>
|
||||
`;
|
||||
|
||||
connectedNodes.forEach(connectedNode => {
|
||||
const connectedData = connectedNode.data();
|
||||
html += `
|
||||
<li style="color: ${connectedData.color || '#60a5fa'}">
|
||||
${connectedData.label || connectedData.name}
|
||||
</li>
|
||||
`;
|
||||
});
|
||||
|
||||
html += `
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
sidebar.innerHTML = html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt die Auswahl zurück
|
||||
* @param {Object} cy - Cytoscape-Instanz
|
||||
*/
|
||||
function resetSelection(cy) {
|
||||
window.mindmapInstance.selectedNode = null;
|
||||
|
||||
// Alle Hervorhebungen zurücksetzen
|
||||
cy.nodes().forEach(node => {
|
||||
node.removeStyle();
|
||||
node.connectedEdges().removeStyle();
|
||||
});
|
||||
|
||||
// Info-Panel ausblenden
|
||||
const infoPanel = document.getElementById('infoPanel');
|
||||
if (infoPanel) {
|
||||
infoPanel.style.display = 'none';
|
||||
}
|
||||
|
||||
// Seitenleiste leeren
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
if (sidebar) {
|
||||
sidebar.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -435,4 +546,6 @@ function initMindmapPage() {
|
||||
];
|
||||
return colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisiere die Mindmap-Seite
|
||||
initMindmapPage();
|
||||
Binary file not shown.
@@ -1,28 +1,538 @@
|
||||
/**
|
||||
* Update Mindmap
|
||||
* Dieses Skript fügt die neuen wissenschaftlichen Knoten zur Mindmap hinzu
|
||||
* und stellt sicher, dass sie korrekt angezeigt werden.
|
||||
* Dieses Skript fügt Knoten zur Mindmap hinzu und stellt sicher,
|
||||
* dass sie im neuronalen Netzwerk-Design angezeigt werden.
|
||||
* Implementiert Lazy Loading & Progressive Disclosure
|
||||
*/
|
||||
|
||||
// Mock-Datenbank für Subthemen (später durch echte DB-Abfragen ersetzen)
|
||||
const subthemesDatabase = {
|
||||
'philosophy': [
|
||||
{
|
||||
id: 'epistemology',
|
||||
label: 'Erkenntnistheorie',
|
||||
category: 'Philosophie',
|
||||
description: 'Untersuchung der Natur und Grenzen menschlicher Erkenntnis',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
id: 'ethics',
|
||||
label: 'Ethik',
|
||||
category: 'Philosophie',
|
||||
description: 'Lehre vom moralisch richtigen Handeln',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
id: 'metaphysics',
|
||||
label: 'Metaphysik',
|
||||
category: 'Philosophie',
|
||||
description: 'Grundfragen des Seins und der Wirklichkeit',
|
||||
hasChildren: true
|
||||
}
|
||||
],
|
||||
'science': [
|
||||
{
|
||||
id: 'physics',
|
||||
label: 'Physik',
|
||||
category: 'Wissenschaft',
|
||||
description: 'Lehre von der Materie und ihren Wechselwirkungen',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
id: 'biology',
|
||||
label: 'Biologie',
|
||||
category: 'Wissenschaft',
|
||||
description: 'Lehre von den Lebewesen und ihren Lebensprozessen',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
id: 'chemistry',
|
||||
label: 'Chemie',
|
||||
category: 'Wissenschaft',
|
||||
description: 'Wissenschaft von den Stoffen und ihren Reaktionen',
|
||||
hasChildren: true
|
||||
}
|
||||
],
|
||||
'technology': [
|
||||
{
|
||||
id: 'ai',
|
||||
label: 'Künstliche Intelligenz',
|
||||
category: 'Technologie',
|
||||
description: 'Maschinelles Lernen und intelligente Systeme',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
id: 'robotics',
|
||||
label: 'Robotik',
|
||||
category: 'Technologie',
|
||||
description: 'Entwicklung und Steuerung von Robotern',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
id: 'quantum_computing',
|
||||
label: 'Quantencomputing',
|
||||
category: 'Technologie',
|
||||
description: 'Computer basierend auf Quantenmechanik',
|
||||
hasChildren: true
|
||||
}
|
||||
],
|
||||
'arts': [
|
||||
{
|
||||
id: 'visual_arts',
|
||||
label: 'Bildende Kunst',
|
||||
category: 'Künste',
|
||||
description: 'Malerei, Bildhauerei und andere visuelle Kunstformen',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
id: 'music',
|
||||
label: 'Musik',
|
||||
category: 'Künste',
|
||||
description: 'Tonkunst und musikalische Komposition',
|
||||
hasChildren: true
|
||||
},
|
||||
{
|
||||
id: 'literature',
|
||||
label: 'Literatur',
|
||||
category: 'Künste',
|
||||
description: 'Schriftliche Kunstwerke und Poesie',
|
||||
hasChildren: true
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
// Icon-Definitionen für Kategorien (FontAwesome oder SVG-URL)
|
||||
const categoryIcons = {
|
||||
'Philosophie': 'fa-solid fa-lightbulb',
|
||||
'Wissenschaft': 'fa-solid fa-atom',
|
||||
'Technologie': 'fa-solid fa-microchip',
|
||||
'Künste': 'fa-solid fa-palette',
|
||||
'Psychologie': 'fa-solid fa-brain'
|
||||
};
|
||||
|
||||
// Farben für Kategorien (wie im Bild)
|
||||
const categoryColors = {
|
||||
'Philosophie': '#b71c1c', // Rot
|
||||
'Wissenschaft': '#f4b400', // Gelb
|
||||
'Technologie': '#0d47a1', // Blau
|
||||
'Künste': '#c2185b', // Pink
|
||||
'Psychologie': '#009688' // Türkis
|
||||
};
|
||||
|
||||
// Initiale Mindmap-Daten (nur oberste Ebene, mit Icon und Farbe)
|
||||
const mindmapData = {
|
||||
nodes: [
|
||||
{
|
||||
id: 'center',
|
||||
label: 'Wissenskarte',
|
||||
isCenter: true,
|
||||
color: '#f5f5f5',
|
||||
icon: 'fa-solid fa-circle',
|
||||
fontColor: '#222',
|
||||
fontSize: 22
|
||||
},
|
||||
{
|
||||
id: 'philosophy',
|
||||
label: 'Philosophie',
|
||||
category: 'Philosophie',
|
||||
description: 'Die Lehre vom Denken und der Erkenntnis',
|
||||
hasChildren: true,
|
||||
expanded: false,
|
||||
neuronSize: 8,
|
||||
neuronActivity: 0.8,
|
||||
color: categoryColors['Philosophie'],
|
||||
icon: categoryIcons['Philosophie'],
|
||||
fontColor: '#fff',
|
||||
fontSize: 18
|
||||
},
|
||||
{
|
||||
id: 'science',
|
||||
label: 'Wissenschaft',
|
||||
category: 'Wissenschaft',
|
||||
description: 'Systematische Erforschung der Natur und Gesellschaft',
|
||||
hasChildren: true,
|
||||
expanded: false,
|
||||
neuronSize: 8,
|
||||
neuronActivity: 0.8,
|
||||
color: categoryColors['Wissenschaft'],
|
||||
icon: categoryIcons['Wissenschaft'],
|
||||
fontColor: '#fff',
|
||||
fontSize: 18
|
||||
},
|
||||
{
|
||||
id: 'technology',
|
||||
label: 'Technologie',
|
||||
category: 'Technologie',
|
||||
description: 'Anwendung wissenschaftlicher Erkenntnisse',
|
||||
hasChildren: true,
|
||||
expanded: false,
|
||||
neuronSize: 8,
|
||||
neuronActivity: 0.8,
|
||||
color: categoryColors['Technologie'],
|
||||
icon: categoryIcons['Technologie'],
|
||||
fontColor: '#fff',
|
||||
fontSize: 18
|
||||
},
|
||||
{
|
||||
id: 'arts',
|
||||
label: 'Künste',
|
||||
category: 'Künste',
|
||||
description: 'Kreativer Ausdruck und künstlerische Gestaltung',
|
||||
hasChildren: true,
|
||||
expanded: false,
|
||||
neuronSize: 8,
|
||||
neuronActivity: 0.8,
|
||||
color: categoryColors['Künste'],
|
||||
icon: categoryIcons['Künste'],
|
||||
fontColor: '#fff',
|
||||
fontSize: 18
|
||||
}
|
||||
],
|
||||
edges: [
|
||||
{ source: 'center', target: 'philosophy', strength: 0.9 },
|
||||
{ source: 'center', target: 'science', strength: 0.9 },
|
||||
{ source: 'center', target: 'technology', strength: 0.9 },
|
||||
{ source: 'center', target: 'arts', strength: 0.9 }
|
||||
]
|
||||
};
|
||||
|
||||
// Warte bis DOM geladen ist
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Prüfe, ob wir auf der Mindmap-Seite sind
|
||||
console.log('DOMContentLoaded Event ausgelöst');
|
||||
|
||||
// Prüfe, ob der Container existiert
|
||||
const cyContainer = document.getElementById('cy');
|
||||
console.log('Container gefunden:', cyContainer);
|
||||
|
||||
if (!cyContainer) {
|
||||
console.log('Kein Mindmap-Container gefunden, überspringe Initialisierung.');
|
||||
console.error('Mindmap-Container #cy nicht gefunden!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Auf das Laden der Mindmap warten
|
||||
document.addEventListener('mindmap-loaded', function() {
|
||||
console.log('Mindmap geladen, füge wissenschaftliche Knoten hinzu...');
|
||||
enhanceMindmap();
|
||||
// Prüfe, ob Cytoscape verfügbar ist
|
||||
if (typeof cytoscape === 'undefined') {
|
||||
console.error('Cytoscape ist nicht definiert!');
|
||||
return;
|
||||
}
|
||||
console.log('Cytoscape ist verfügbar');
|
||||
|
||||
// Beispiel-Daten entfernt, stattdessen große Mindmap-Daten verwenden
|
||||
const elements = [
|
||||
// Knoten
|
||||
...mindmapData.nodes.map(node => ({
|
||||
data: {
|
||||
id: node.id,
|
||||
label: node.label,
|
||||
category: node.category,
|
||||
description: node.description,
|
||||
hasChildren: node.hasChildren,
|
||||
expanded: node.expanded,
|
||||
neuronSize: node.neuronSize,
|
||||
neuronActivity: node.neuronActivity,
|
||||
color: node.color,
|
||||
icon: node.icon,
|
||||
fontColor: node.fontColor,
|
||||
fontSize: node.fontSize
|
||||
}
|
||||
})),
|
||||
// Kanten
|
||||
...mindmapData.edges.map(edge => ({
|
||||
data: {
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
label: edge.label,
|
||||
strength: edge.strength
|
||||
}
|
||||
}))
|
||||
];
|
||||
|
||||
console.log('Initialisiere Cytoscape...');
|
||||
|
||||
// Initialisiere Cytoscape mit concentric/circle Layout und Icon-Overlay
|
||||
window.cy = cytoscape({
|
||||
container: cyContainer,
|
||||
elements: elements,
|
||||
style: [
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'background-color': 'data(color)',
|
||||
'label': 'data(label)',
|
||||
'color': 'data(fontColor)',
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
'font-size': 'data(fontSize)',
|
||||
'width': 'mapData(neuronSize, 3, 10, 60, 110)',
|
||||
'height': 'mapData(neuronSize, 3, 10, 60, 110)',
|
||||
'border-width': 4,
|
||||
'border-color': '#fff',
|
||||
'border-opacity': 0.9,
|
||||
'overlay-padding': 4,
|
||||
'z-index': 10,
|
||||
'shape': 'ellipse',
|
||||
'background-opacity': 0.95,
|
||||
'shadow-blur': 20,
|
||||
'shadow-color': 'data(color)',
|
||||
'shadow-opacity': 0.7,
|
||||
'shadow-offset-x': 0,
|
||||
'shadow-offset-y': 0,
|
||||
'text-wrap': 'wrap',
|
||||
'text-max-width': 90
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'node[isCenter]','style': {
|
||||
'background-color': '#f5f5f5',
|
||||
'color': '#222',
|
||||
'font-size': 22,
|
||||
'border-width': 0,
|
||||
'width': 120,
|
||||
'height': 120
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'node:selected',
|
||||
style: {
|
||||
'border-color': '#f59e42',
|
||||
'border-width': 6,
|
||||
'shadow-blur': 30,
|
||||
'shadow-opacity': 0.9
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': 'mapData(strength, 0.2, 1, 3, 7)',
|
||||
'line-color': '#bdbdbd',
|
||||
'line-opacity': 0.7,
|
||||
'curve-style': 'bezier',
|
||||
'target-arrow-shape': 'none',
|
||||
'control-point-distances': [40, -40],
|
||||
'control-point-weights': [0.5, 0.5],
|
||||
'edge-distances': 'intersection',
|
||||
'line-style': 'solid'
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: {
|
||||
name: 'concentric',
|
||||
fit: true,
|
||||
padding: 60,
|
||||
animate: true,
|
||||
concentric: function(node) {
|
||||
return node.data('isCenter') ? 2 : 1;
|
||||
},
|
||||
levelWidth: function() { return 1; }
|
||||
}
|
||||
});
|
||||
|
||||
console.log('Cytoscape initialisiert');
|
||||
|
||||
// Füge neuronale Eigenschaften zu allen Knoten hinzu
|
||||
cy.nodes().forEach(node => {
|
||||
const data = node.data();
|
||||
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: categoryColors[data.category] || '#60a5fa'
|
||||
});
|
||||
});
|
||||
|
||||
// Füge synaptische Eigenschaften zu allen Kanten hinzu
|
||||
cy.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
|
||||
});
|
||||
});
|
||||
|
||||
// Starte neuronale Aktivitätssimulation
|
||||
startNeuralActivitySimulation(cy);
|
||||
|
||||
// Mindmap mit echten Daten befüllen (Styles, Farben etc.)
|
||||
updateMindmap();
|
||||
|
||||
// Event auslösen, damit andere Scripte reagieren können
|
||||
document.dispatchEvent(new Event('mindmap-loaded'));
|
||||
console.log('mindmap-loaded Event ausgelöst');
|
||||
|
||||
// Event-Listener für Knoten-Klicks
|
||||
cy.on('tap', 'node', async function(evt) {
|
||||
const node = evt.target;
|
||||
console.log('Node clicked:', node.id(), 'hasChildren:', node.data('hasChildren'), 'expanded:', node.data('expanded'));
|
||||
|
||||
if (node.data('hasChildren') && !node.data('expanded')) {
|
||||
await loadSubthemes(node);
|
||||
}
|
||||
});
|
||||
|
||||
// Nach dem Rendern: Icons als HTML-Overlay einfügen
|
||||
setTimeout(() => {
|
||||
cy.nodes().forEach(node => {
|
||||
const icon = node.data('icon');
|
||||
if (icon) {
|
||||
const dom = cy.getElementById(node.id()).popperRef();
|
||||
const iconDiv = document.createElement('div');
|
||||
iconDiv.className = 'cy-node-icon';
|
||||
iconDiv.innerHTML = `<i class="${icon}" style="font-size:2.2em;"></i>`;
|
||||
document.body.appendChild(iconDiv);
|
||||
// Positionierung mit Popper.js oder absolut über node.position()
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Funktion zum Initialisieren des neuronalen Designs
|
||||
function initializeNeuralDesign(cy) {
|
||||
// Füge neuronale Eigenschaften zu allen Knoten hinzu
|
||||
cy.nodes().forEach(node => {
|
||||
const data = node.data();
|
||||
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: categoryColors[data.category] || '#60a5fa'
|
||||
});
|
||||
});
|
||||
|
||||
// Füge synaptische Eigenschaften zu allen Kanten hinzu
|
||||
cy.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
|
||||
});
|
||||
});
|
||||
|
||||
// Wende neuronales Styling an
|
||||
cy.style()
|
||||
.selector('node')
|
||||
.style({
|
||||
'background-color': 'data(color)',
|
||||
'label': 'data(label)',
|
||||
'color': '#fff',
|
||||
'text-background-color': 'rgba(0, 0, 0, 0.7)',
|
||||
'text-background-opacity': 0.8,
|
||||
'text-background-padding': '4px',
|
||||
'text-valign': 'center',
|
||||
'text-halign': 'center',
|
||||
'font-size': 16,
|
||||
'width': 'mapData(neuronSize, 3, 10, 30, 60)',
|
||||
'height': 'mapData(neuronSize, 3, 10, 30, 60)',
|
||||
'border-width': 2,
|
||||
'border-color': '#fff',
|
||||
'border-opacity': 0.8,
|
||||
'overlay-padding': 4,
|
||||
'z-index': 10,
|
||||
'shape': 'ellipse',
|
||||
'background-opacity': 0.85,
|
||||
'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 10, 20)',
|
||||
'shadow-color': 'data(color)',
|
||||
'shadow-opacity': 0.6,
|
||||
'shadow-offset-x': 0,
|
||||
'shadow-offset-y': 0
|
||||
})
|
||||
.selector('edge')
|
||||
.style({
|
||||
'width': 'mapData(strength, 0.2, 1, 1, 3)',
|
||||
'line-color': '#a78bfa',
|
||||
'line-opacity': 'mapData(strength, 0.2, 1, 0.4, 0.8)',
|
||||
'target-arrow-color': '#a78bfa',
|
||||
'target-arrow-shape': 'none',
|
||||
'curve-style': 'bezier',
|
||||
'control-point-distances': [20, -20],
|
||||
'control-point-weights': [0.5, 0.5],
|
||||
'edge-distances': 'intersection',
|
||||
'loop-direction': '-45deg',
|
||||
'loop-sweep': '-90deg',
|
||||
'line-style': function(ele) {
|
||||
const strength = ele.data('strength');
|
||||
if (strength <= 0.4) return 'dotted';
|
||||
if (strength <= 0.6) return 'dashed';
|
||||
return 'solid';
|
||||
}
|
||||
})
|
||||
.update();
|
||||
|
||||
// Starte neuronale Aktivitätssimulation
|
||||
startNeuralActivitySimulation(cy);
|
||||
}
|
||||
|
||||
// Modifiziere die updateMindmap Funktion
|
||||
function updateMindmap() {
|
||||
if (!cy) return;
|
||||
|
||||
// Bestehende Elemente entfernen
|
||||
cy.elements().remove();
|
||||
|
||||
// Neue Knoten hinzufügen
|
||||
mindmapData.nodes.forEach(node => {
|
||||
cy.add({
|
||||
group: 'nodes',
|
||||
data: {
|
||||
id: node.id,
|
||||
label: node.label,
|
||||
category: node.category,
|
||||
description: node.description,
|
||||
hasChildren: node.hasChildren,
|
||||
expanded: node.expanded,
|
||||
neuronSize: node.neuronSize,
|
||||
neuronActivity: node.neuronActivity,
|
||||
color: node.color,
|
||||
icon: node.icon,
|
||||
fontColor: node.fontColor,
|
||||
fontSize: node.fontSize
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Neue Kanten hinzufügen
|
||||
mindmapData.edges.forEach(edge => {
|
||||
cy.add({
|
||||
group: 'edges',
|
||||
data: {
|
||||
source: edge.source,
|
||||
target: edge.target,
|
||||
strength: edge.strength
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Neuronales Design initialisieren
|
||||
initializeNeuralDesign(cy);
|
||||
|
||||
// Layout anwenden
|
||||
cy.layout({
|
||||
name: 'cose',
|
||||
animate: true,
|
||||
animationDuration: 1000,
|
||||
nodeDimensionsIncludeLabels: true,
|
||||
padding: 100,
|
||||
spacingFactor: 1.8,
|
||||
randomize: false,
|
||||
fit: true,
|
||||
componentSpacing: 100,
|
||||
nodeRepulsion: 8000,
|
||||
edgeElasticity: 100,
|
||||
nestingFactor: 1.2,
|
||||
gravity: 80
|
||||
}).run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Erweitert die Mindmap mit den neu hinzugefügten wissenschaftlichen Knoten
|
||||
* Erweitert die Mindmap mit dem neuronalen Netzwerk-Design
|
||||
*/
|
||||
function enhanceMindmap() {
|
||||
// Auf die bestehende Cytoscape-Instanz zugreifen
|
||||
@@ -33,25 +543,226 @@ function enhanceMindmap() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Aktualisiere das Layout mit zusätzlichem Platz für die neuen Knoten
|
||||
// Aktualisiere das Layout für eine bessere Verteilung
|
||||
cy.layout({
|
||||
name: 'cose',
|
||||
animate: true,
|
||||
animationDuration: 800,
|
||||
animationDuration: 1800,
|
||||
nodeDimensionsIncludeLabels: true,
|
||||
padding: 100,
|
||||
spacingFactor: 1.8,
|
||||
randomize: false,
|
||||
fit: true
|
||||
fit: true,
|
||||
componentSpacing: 100,
|
||||
nodeRepulsion: 8000,
|
||||
edgeElasticity: 100,
|
||||
nestingFactor: 1.2,
|
||||
gravity: 80
|
||||
}).run();
|
||||
|
||||
console.log('Mindmap wurde erfolgreich aktualisiert!');
|
||||
// Neuronen-Namen mit besserer Lesbarkeit umgestalten
|
||||
cy.style()
|
||||
.selector('node')
|
||||
.style({
|
||||
'text-background-color': 'rgba(10, 14, 25, 0.7)',
|
||||
'text-background-opacity': 0.7,
|
||||
'text-background-padding': '2px',
|
||||
'text-border-opacity': 0.2,
|
||||
'text-border-width': 1,
|
||||
'text-border-color': '#8b5cf6'
|
||||
})
|
||||
.update();
|
||||
|
||||
// Wenn ein Wissen-Knoten existiert, sicherstellen, dass er im Zentrum ist
|
||||
const rootNode = cy.getElementById('1');
|
||||
if (rootNode.length > 0) {
|
||||
cy.center(rootNode);
|
||||
// Sicherstellen, dass alle Knoten Neuronen-Eigenschaften haben
|
||||
cy.nodes().forEach(node => {
|
||||
if (!node.data('neuronSize')) {
|
||||
const neuronSize = Math.floor(Math.random() * 8) + 3;
|
||||
node.data('neuronSize', neuronSize);
|
||||
}
|
||||
|
||||
if (!node.data('neuronActivity')) {
|
||||
const neuronActivity = Math.random() * 0.7 + 0.3;
|
||||
node.data('neuronActivity', neuronActivity);
|
||||
}
|
||||
|
||||
// Zusätzliche Neuronale Eigenschaften
|
||||
node.data('pulseFrequency', Math.random() * 4 + 2); // Pulsfrequenz (2-6 Hz)
|
||||
node.data('refractionPeriod', Math.random() * 300 + 700); // Refraktionszeit (700-1000ms)
|
||||
node.data('threshold', Math.random() * 0.3 + 0.6); // Aktivierungsschwelle (0.6-0.9)
|
||||
});
|
||||
|
||||
// Sicherstellen, dass alle Kanten Synapse-Eigenschaften haben
|
||||
cy.edges().forEach(edge => {
|
||||
if (!edge.data('strength')) {
|
||||
const strength = Math.random() * 0.6 + 0.2;
|
||||
edge.data('strength', strength);
|
||||
}
|
||||
|
||||
// Zusätzliche synaptische Eigenschaften
|
||||
edge.data('conductionVelocity', Math.random() * 0.5 + 0.3); // Leitungsgeschwindigkeit (0.3-0.8)
|
||||
edge.data('latency', Math.random() * 100 + 50); // Signalverzögerung (50-150ms)
|
||||
});
|
||||
|
||||
// Neuronales Netzwerk-Stil anwenden
|
||||
applyNeuralNetworkStyle(cy);
|
||||
|
||||
console.log('Mindmap wurde erfolgreich im neuronalen Netzwerk-Stil aktualisiert');
|
||||
|
||||
// Spezielle Effekte für das neuronale Netzwerk hinzufügen
|
||||
startNeuralActivitySimulation(cy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wendet detaillierte neuronale Netzwerkstile auf die Mindmap an
|
||||
* @param {Object} cy - Cytoscape-Instanz
|
||||
*/
|
||||
function applyNeuralNetworkStyle(cy) {
|
||||
// Wende erweiterte Stile für Neuronen und Synapsen an
|
||||
cy.style()
|
||||
.selector('node')
|
||||
.style({
|
||||
'label': 'data(name)',
|
||||
'text-valign': 'bottom',
|
||||
'text-halign': 'center',
|
||||
'color': '#ffffff',
|
||||
'text-outline-width': 1.5,
|
||||
'text-outline-color': '#0a0e19',
|
||||
'text-outline-opacity': 0.9,
|
||||
'font-size': 10,
|
||||
'text-margin-y': 7,
|
||||
'width': 'mapData(neuronSize, 3, 10, 15, 40)',
|
||||
'height': 'mapData(neuronSize, 3, 10, 15, 40)',
|
||||
'background-color': 'data(color)',
|
||||
'background-opacity': 0.85,
|
||||
'border-width': 0,
|
||||
'shape': 'ellipse',
|
||||
'shadow-blur': 'mapData(neuronActivity, 0.3, 1, 5, 15)',
|
||||
'shadow-color': 'data(color)',
|
||||
'shadow-opacity': 0.6,
|
||||
'shadow-offset-x': 0,
|
||||
'shadow-offset-y': 0
|
||||
})
|
||||
.selector('edge')
|
||||
.style({
|
||||
'width': 'mapData(strength, 0.2, 0.8, 0.7, 2)',
|
||||
'curve-style': 'bezier',
|
||||
'line-color': '#8a8aaa',
|
||||
'line-opacity': 'mapData(strength, 0.2, 0.8, 0.4, 0.7)',
|
||||
'line-style': function(ele) {
|
||||
const strength = ele.data('strength');
|
||||
if (strength <= 0.4) return 'dotted';
|
||||
if (strength <= 0.6) return 'dashed';
|
||||
return 'solid';
|
||||
},
|
||||
'target-arrow-shape': 'none',
|
||||
'source-endpoint': '0% 50%',
|
||||
'target-endpoint': '100% 50%'
|
||||
})
|
||||
.selector('node[isRoot]')
|
||||
.style({
|
||||
'font-size': 12,
|
||||
'font-weight': 'bold',
|
||||
'width': 50,
|
||||
'height': 50,
|
||||
'background-color': '#6366f1',
|
||||
'shadow-blur': 20,
|
||||
'shadow-color': '#6366f1',
|
||||
'shadow-opacity': 0.8,
|
||||
'text-margin-y': 8
|
||||
})
|
||||
.update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Simuliert neuronale Aktivität in der Mindmap
|
||||
* @param {Object} cy - Cytoscape-Instanz
|
||||
*/
|
||||
function startNeuralActivitySimulation(cy) {
|
||||
if (window.neuralInterval) clearInterval(window.neuralInterval);
|
||||
|
||||
const nodes = cy.nodes();
|
||||
const edges = cy.edges();
|
||||
let currentTime = Date.now();
|
||||
|
||||
// Neuronale Aktivität simulieren
|
||||
function simulateNeuralActivity() {
|
||||
currentTime = Date.now();
|
||||
|
||||
// Zufällige Neuronen "feuern" lassen
|
||||
nodes.forEach(node => {
|
||||
const data = node.data();
|
||||
const lastFired = data.lastFired || 0;
|
||||
const timeSinceLastFire = currentTime - lastFired;
|
||||
|
||||
// Prüfen ob Neuron feuern kann (Refraktionsperiode)
|
||||
if (timeSinceLastFire > data.refractionPeriod) {
|
||||
// Zufälliges Feuern basierend auf Aktivität
|
||||
if (Math.random() < data.neuronActivity * 0.1) {
|
||||
fireNeuron(node, true, currentTime);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Neuron feuern lassen
|
||||
function fireNeuron(node, state, currentTime) {
|
||||
const data = node.data();
|
||||
data.lastFired = currentTime;
|
||||
|
||||
// Visuelles Feedback
|
||||
node.style({
|
||||
'background-opacity': 1,
|
||||
'shadow-blur': 25,
|
||||
'shadow-opacity': 0.9
|
||||
});
|
||||
|
||||
// Nach kurzer Zeit zurück zum Normalzustand
|
||||
setTimeout(() => {
|
||||
node.style({
|
||||
'background-opacity': 0.85,
|
||||
'shadow-blur': 18,
|
||||
'shadow-opacity': 0.6
|
||||
});
|
||||
}, 200);
|
||||
|
||||
// Signal weiterleiten
|
||||
if (state) {
|
||||
propagateSignal(node, currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Signal über Kanten weiterleiten
|
||||
function propagateSignal(sourceNode, currentTime) {
|
||||
const outgoingEdges = sourceNode.connectedEdges('out');
|
||||
|
||||
outgoingEdges.forEach(edge => {
|
||||
const targetNode = edge.target();
|
||||
const edgeData = edge.data();
|
||||
const latency = edgeData.latency;
|
||||
|
||||
// Signal mit Verzögerung weiterleiten
|
||||
setTimeout(() => {
|
||||
const targetData = targetNode.data();
|
||||
const timeSinceLastFire = currentTime - (targetData.lastFired || 0);
|
||||
|
||||
// Prüfen ob Zielneuron feuern kann
|
||||
if (timeSinceLastFire > targetData.refractionPeriod) {
|
||||
// Signalstärke berechnen
|
||||
const signalStrength = edgeData.strength *
|
||||
edgeData.conductionVelocity *
|
||||
sourceNode.data('neuronActivity');
|
||||
|
||||
// Neuron feuern lassen wenn Signal stark genug
|
||||
if (signalStrength > targetData.threshold) {
|
||||
fireNeuron(targetNode, true, currentTime + latency);
|
||||
}
|
||||
}
|
||||
}, latency);
|
||||
});
|
||||
}
|
||||
|
||||
// Simulation starten
|
||||
window.neuralInterval = setInterval(simulateNeuralActivity, 100);
|
||||
}
|
||||
|
||||
// Hilfe-Funktion zum Hinzufügen eines Flash-Hinweises
|
||||
@@ -90,4 +801,204 @@ function createFlashContainer() {
|
||||
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 {
|
||||
console.log('Loading subthemes for node:', node.id());
|
||||
|
||||
// Simuliere Datenbankabfrage
|
||||
const subthemes = subthemesDatabase[node.id()];
|
||||
|
||||
if (!subthemes) {
|
||||
console.log('No subthemes found for node:', node.id());
|
||||
return;
|
||||
}
|
||||
|
||||
// Animation starten
|
||||
showFlash('Lade Subthemen...', 'info');
|
||||
|
||||
// Neue Seite erstellen
|
||||
const mindmapContainer = document.querySelector('.mindmap-container');
|
||||
const newPage = document.createElement('div');
|
||||
newPage.className = 'mindmap-page';
|
||||
newPage.style.display = 'none';
|
||||
|
||||
// Kopfzeile erstellen
|
||||
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>
|
||||
`;
|
||||
|
||||
// Container für die neue Mindmap
|
||||
const newContainer = document.createElement('div');
|
||||
newContainer.id = `cy-${node.id()}`;
|
||||
newContainer.className = 'mindmap-view';
|
||||
|
||||
// Elemente zur Seite hinzufügen
|
||||
newPage.appendChild(header);
|
||||
newPage.appendChild(newContainer);
|
||||
mindmapContainer.appendChild(newPage);
|
||||
|
||||
// Neue Cytoscape-Instanz erstellen
|
||||
const newCy = cytoscape({
|
||||
container: newContainer,
|
||||
elements: [],
|
||||
style: cy.style(),
|
||||
layout: {
|
||||
name: 'cose',
|
||||
animate: true,
|
||||
animationDuration: 500,
|
||||
refresh: 20,
|
||||
fit: true,
|
||||
padding: 30,
|
||||
nodeRepulsion: 4500,
|
||||
idealEdgeLength: 50,
|
||||
edgeElasticity: 0.45
|
||||
}
|
||||
});
|
||||
|
||||
// Neue Knoten hinzufügen
|
||||
subthemes.forEach(subtheme => {
|
||||
newCy.add({
|
||||
group: 'nodes',
|
||||
data: {
|
||||
id: subtheme.id,
|
||||
label: subtheme.label,
|
||||
category: subtheme.category,
|
||||
description: subtheme.description,
|
||||
hasChildren: subtheme.hasChildren,
|
||||
expanded: false,
|
||||
neuronSize: 6,
|
||||
neuronActivity: 0.7
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Neuronales Design für die neue Mindmap initialisieren
|
||||
initializeNeuralDesign(newCy);
|
||||
|
||||
// Event-Listener für die neue Mindmap
|
||||
newCy.on('tap', 'node', async function(evt) {
|
||||
const clickedNode = evt.target;
|
||||
if (clickedNode.data('hasChildren') && !clickedNode.data('expanded')) {
|
||||
await loadSubthemes(clickedNode);
|
||||
}
|
||||
});
|
||||
|
||||
// Alte Seite ausblenden und neue anzeigen
|
||||
cy.container().style.display = 'none';
|
||||
newPage.style.display = 'block';
|
||||
|
||||
// Layout ausführen
|
||||
newCy.layout().run();
|
||||
|
||||
// Erfolgsmeldung anzeigen
|
||||
showFlash('Subthemen erfolgreich geladen', 'success');
|
||||
|
||||
// Icons für Subthemen wie oben einfügen
|
||||
setTimeout(() => {
|
||||
newCy.nodes().forEach(node => {
|
||||
const icon = node.data('icon');
|
||||
if (icon) {
|
||||
const dom = newCy.getElementById(node.id()).popperRef();
|
||||
const iconDiv = document.createElement('div');
|
||||
iconDiv.className = 'cy-node-icon';
|
||||
iconDiv.innerHTML = `<i class="${icon}" style="font-size:2em;"></i>`;
|
||||
document.body.appendChild(iconDiv);
|
||||
}
|
||||
});
|
||||
}, 500);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Subthemen:', error);
|
||||
showFlash('Fehler beim Laden der Subthemen', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Funktion zum Zurücknavigieren
|
||||
function goBack() {
|
||||
const currentPage = document.querySelector('.mindmap-page:not([style*="display: none"])');
|
||||
if (currentPage) {
|
||||
currentPage.style.display = 'none';
|
||||
cy.container().style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
2061
static/mindmap.js
2061
static/mindmap.js
File diff suppressed because it is too large
Load Diff
@@ -111,12 +111,6 @@
|
||||
|
||||
<!-- ChatGPT Assistant -->
|
||||
<script src="{{ url_for('static', filename='js/modules/chatgpt-assistant.js') }}"></script>
|
||||
|
||||
<!-- MindMap Visualization Module -->
|
||||
<script src="{{ url_for('static', filename='js/modules/mindmap.js') }}"></script>
|
||||
|
||||
<!-- MindMap Page Module -->
|
||||
<script src="{{ url_for('static', filename='js/modules/mindmap-page.js') }}"></script>
|
||||
|
||||
<!-- Neural Network Background Script -->
|
||||
<script src="{{ url_for('static', filename='neural-network-background.js') }}"></script>
|
||||
@@ -740,205 +734,207 @@
|
||||
|
||||
<!-- Hilfsscripts -->
|
||||
{% block scripts %}{% endblock %}
|
||||
{% block extra_js %}{% endblock %}
|
||||
|
||||
<!-- KI-Chat Initialisierung -->
|
||||
<!-- ChatGPT Initialisierung -->
|
||||
<script>
|
||||
// ChatGPT-Assistent Klasse
|
||||
class ChatGPTAssistant {
|
||||
constructor() {
|
||||
this.chatContainer = null;
|
||||
this.messages = [];
|
||||
this.isOpen = false;
|
||||
}
|
||||
|
||||
init() {
|
||||
// Chat-Container erstellen, falls noch nicht vorhanden
|
||||
if (!document.getElementById('chat-assistant-container')) {
|
||||
this.createChatInterface();
|
||||
// Prüfe, ob ChatGPTAssistant bereits existiert
|
||||
if (typeof ChatGPTAssistant === 'undefined') {
|
||||
class ChatGPTAssistant {
|
||||
constructor() {
|
||||
this.chatContainer = null;
|
||||
this.messages = [];
|
||||
this.isOpen = false;
|
||||
}
|
||||
|
||||
// Event-Listener für Chat-Button
|
||||
const chatButton = document.getElementById('chat-assistant-button');
|
||||
if (chatButton) {
|
||||
chatButton.addEventListener('click', () => this.toggleChat());
|
||||
init() {
|
||||
// Chat-Container erstellen, falls noch nicht vorhanden
|
||||
if (!document.getElementById('chat-assistant-container')) {
|
||||
this.createChatInterface();
|
||||
}
|
||||
|
||||
// Event-Listener für Chat-Button
|
||||
const chatButton = document.getElementById('chat-assistant-button');
|
||||
if (chatButton) {
|
||||
chatButton.addEventListener('click', () => this.toggleChat());
|
||||
}
|
||||
|
||||
// Event-Listener für Senden-Button
|
||||
const sendButton = document.getElementById('chat-send-button');
|
||||
if (sendButton) {
|
||||
sendButton.addEventListener('click', () => this.sendMessage());
|
||||
}
|
||||
|
||||
// Event-Listener für Eingabefeld (Enter-Taste)
|
||||
const inputField = document.getElementById('chat-input');
|
||||
if (inputField) {
|
||||
inputField.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('KI-Assistent erfolgreich initialisiert');
|
||||
}
|
||||
|
||||
// Event-Listener für Senden-Button
|
||||
const sendButton = document.getElementById('chat-send-button');
|
||||
if (sendButton) {
|
||||
sendButton.addEventListener('click', () => this.sendMessage());
|
||||
}
|
||||
|
||||
// Event-Listener für Eingabefeld (Enter-Taste)
|
||||
const inputField = document.getElementById('chat-input');
|
||||
if (inputField) {
|
||||
inputField.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
this.sendMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log('KI-Assistent erfolgreich initialisiert');
|
||||
}
|
||||
|
||||
createChatInterface() {
|
||||
// Chat-Button erstellen
|
||||
const chatButton = document.createElement('button');
|
||||
chatButton.id = 'chat-assistant-button';
|
||||
chatButton.className = 'fixed bottom-6 right-6 bg-primary-600 text-white rounded-full p-4 shadow-lg z-50 hover:bg-primary-700 transition-all';
|
||||
chatButton.innerHTML = '<i class="fas fa-robot text-xl"></i>';
|
||||
document.body.appendChild(chatButton);
|
||||
|
||||
// Chat-Container erstellen
|
||||
const chatContainer = document.createElement('div');
|
||||
chatContainer.id = 'chat-assistant-container';
|
||||
chatContainer.className = 'fixed bottom-24 right-6 w-80 md:w-96 bg-white dark:bg-gray-800 rounded-xl shadow-xl z-50 flex flex-col transition-all duration-300 transform scale-0 origin-bottom-right';
|
||||
chatContainer.style.height = '500px';
|
||||
chatContainer.style.maxHeight = '70vh';
|
||||
|
||||
// Chat-Header
|
||||
chatContainer.innerHTML = `
|
||||
<div class="p-4 border-b dark:border-gray-700 flex justify-between items-center">
|
||||
<h3 class="font-bold text-gray-800 dark:text-white">Systades Assistent</h3>
|
||||
<button id="chat-close-button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div id="chat-messages" class="flex-1 overflow-y-auto p-4 space-y-4"></div>
|
||||
<div class="p-4 border-t dark:border-gray-700">
|
||||
<div class="flex space-x-2">
|
||||
<input id="chat-input" type="text" placeholder="Frage stellen..." class="flex-1 px-4 py-2 rounded-lg border dark:border-gray-700 dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500">
|
||||
<button id="chat-send-button" class="bg-primary-600 text-white px-4 py-2 rounded-lg hover:bg-primary-700 transition-all">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
createChatInterface() {
|
||||
// Chat-Button erstellen
|
||||
const chatButton = document.createElement('button');
|
||||
chatButton.id = 'chat-assistant-button';
|
||||
chatButton.className = 'fixed bottom-6 right-6 bg-primary-600 text-white rounded-full p-4 shadow-lg z-50 hover:bg-primary-700 transition-all';
|
||||
chatButton.innerHTML = '<i class="fas fa-robot text-xl"></i>';
|
||||
document.body.appendChild(chatButton);
|
||||
|
||||
// Chat-Container erstellen
|
||||
const chatContainer = document.createElement('div');
|
||||
chatContainer.id = 'chat-assistant-container';
|
||||
chatContainer.className = 'fixed bottom-24 right-6 w-80 md:w-96 bg-white dark:bg-gray-800 rounded-xl shadow-xl z-50 flex flex-col transition-all duration-300 transform scale-0 origin-bottom-right';
|
||||
chatContainer.style.height = '500px';
|
||||
chatContainer.style.maxHeight = '70vh';
|
||||
|
||||
// Chat-Header
|
||||
chatContainer.innerHTML = `
|
||||
<div class="p-4 border-b dark:border-gray-700 flex justify-between items-center">
|
||||
<h3 class="font-bold text-gray-800 dark:text-white">Systades Assistent</h3>
|
||||
<button id="chat-close-button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(chatContainer);
|
||||
this.chatContainer = chatContainer;
|
||||
|
||||
// Event-Listener für Schließen-Button
|
||||
const closeButton = document.getElementById('chat-close-button');
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', () => this.toggleChat());
|
||||
}
|
||||
}
|
||||
|
||||
toggleChat() {
|
||||
this.isOpen = !this.isOpen;
|
||||
if (this.isOpen) {
|
||||
this.chatContainer.classList.remove('scale-0');
|
||||
this.chatContainer.classList.add('scale-100');
|
||||
} else {
|
||||
this.chatContainer.classList.remove('scale-100');
|
||||
this.chatContainer.classList.add('scale-0');
|
||||
}
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
const inputField = document.getElementById('chat-input');
|
||||
const messageText = inputField.value.trim();
|
||||
|
||||
if (!messageText) return;
|
||||
|
||||
// Benutzer-Nachricht anzeigen
|
||||
this.addMessage('user', messageText);
|
||||
inputField.value = '';
|
||||
|
||||
// Lade-Indikator anzeigen
|
||||
this.addMessage('assistant', '...', 'loading-message');
|
||||
|
||||
try {
|
||||
// API-Anfrage senden
|
||||
const response = await fetch('/api/assistant', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: this.messages.map(msg => ({
|
||||
role: msg.role,
|
||||
content: msg.content
|
||||
}))
|
||||
})
|
||||
});
|
||||
<div id="chat-messages" class="flex-1 overflow-y-auto p-4 space-y-4"></div>
|
||||
<div class="p-4 border-t dark:border-gray-700">
|
||||
<div class="flex space-x-2">
|
||||
<input id="chat-input" type="text" placeholder="Frage stellen..." class="flex-1 px-4 py-2 rounded-lg border dark:border-gray-700 dark:bg-gray-700 dark:text-white focus:outline-none focus:ring-2 focus:ring-primary-500">
|
||||
<button id="chat-send-button" class="bg-primary-600 text-white px-4 py-2 rounded-lg hover:bg-primary-700 transition-all">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const data = await response.json();
|
||||
document.body.appendChild(chatContainer);
|
||||
this.chatContainer = chatContainer;
|
||||
|
||||
// Lade-Nachricht entfernen
|
||||
const loadingMessage = document.getElementById('loading-message');
|
||||
if (loadingMessage) {
|
||||
loadingMessage.remove();
|
||||
// Event-Listener für Schließen-Button
|
||||
const closeButton = document.getElementById('chat-close-button');
|
||||
if (closeButton) {
|
||||
closeButton.addEventListener('click', () => this.toggleChat());
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
this.addMessage('assistant', 'Entschuldigung, es ist ein Fehler aufgetreten: ' + data.error);
|
||||
}
|
||||
|
||||
toggleChat() {
|
||||
this.isOpen = !this.isOpen;
|
||||
if (this.isOpen) {
|
||||
this.chatContainer.classList.remove('scale-0');
|
||||
this.chatContainer.classList.add('scale-100');
|
||||
} else {
|
||||
this.addMessage('assistant', data.response);
|
||||
this.chatContainer.classList.remove('scale-100');
|
||||
this.chatContainer.classList.add('scale-0');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der API-Anfrage:', error);
|
||||
}
|
||||
|
||||
async sendMessage() {
|
||||
const inputField = document.getElementById('chat-input');
|
||||
const messageText = inputField.value.trim();
|
||||
|
||||
// Lade-Nachricht entfernen
|
||||
const loadingMessage = document.getElementById('loading-message');
|
||||
if (loadingMessage) {
|
||||
loadingMessage.remove();
|
||||
if (!messageText) return;
|
||||
|
||||
// Benutzer-Nachricht anzeigen
|
||||
this.addMessage('user', messageText);
|
||||
inputField.value = '';
|
||||
|
||||
// Lade-Indikator anzeigen
|
||||
this.addMessage('assistant', '...', 'loading-message');
|
||||
|
||||
try {
|
||||
// API-Anfrage senden
|
||||
const response = await fetch('/api/assistant', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
messages: this.messages.map(msg => ({
|
||||
role: msg.role,
|
||||
content: msg.content
|
||||
}))
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Lade-Nachricht entfernen
|
||||
const loadingMessage = document.getElementById('loading-message');
|
||||
if (loadingMessage) {
|
||||
loadingMessage.remove();
|
||||
}
|
||||
|
||||
if (data.error) {
|
||||
this.addMessage('assistant', 'Entschuldigung, es ist ein Fehler aufgetreten: ' + data.error);
|
||||
} else {
|
||||
this.addMessage('assistant', data.response);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler bei der API-Anfrage:', error);
|
||||
|
||||
// Lade-Nachricht entfernen
|
||||
const loadingMessage = document.getElementById('loading-message');
|
||||
if (loadingMessage) {
|
||||
loadingMessage.remove();
|
||||
}
|
||||
|
||||
this.addMessage('assistant', 'Entschuldigung, es ist ein Fehler aufgetreten. Bitte versuche es später erneut.');
|
||||
}
|
||||
}
|
||||
|
||||
addMessage(role, content, id = null) {
|
||||
const messagesContainer = document.getElementById('chat-messages');
|
||||
|
||||
// Nachricht zum Array hinzufügen (außer Lade-Nachrichten)
|
||||
if (id !== 'loading-message') {
|
||||
this.messages.push({ role, content });
|
||||
}
|
||||
|
||||
this.addMessage('assistant', 'Entschuldigung, es ist ein Fehler aufgetreten. Bitte versuche es später erneut.');
|
||||
// Nachricht zum DOM hinzufügen
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.className = `p-3 rounded-lg ${role === 'user' ? 'bg-primary-100 dark:bg-primary-900/30 ml-6' : 'bg-gray-100 dark:bg-gray-700 mr-6'}`;
|
||||
if (id) {
|
||||
messageElement.id = id;
|
||||
}
|
||||
|
||||
messageElement.innerHTML = `
|
||||
<div class="flex items-start">
|
||||
<div class="w-8 h-8 rounded-full flex items-center justify-center ${role === 'user' ? 'bg-primary-600' : 'bg-gray-600'} text-white mr-2">
|
||||
<i class="fas ${role === 'user' ? 'fa-user' : 'fa-robot'} text-xs"></i>
|
||||
</div>
|
||||
<div class="flex-1 text-sm ${role === 'user' ? 'text-gray-800 dark:text-gray-200' : 'text-gray-700 dark:text-gray-300'}">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
messagesContainer.appendChild(messageElement);
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
addMessage(role, content, id = null) {
|
||||
const messagesContainer = document.getElementById('chat-messages');
|
||||
|
||||
// Nachricht zum Array hinzufügen (außer Lade-Nachrichten)
|
||||
if (id !== 'loading-message') {
|
||||
this.messages.push({ role, content });
|
||||
}
|
||||
|
||||
// Nachricht zum DOM hinzufügen
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.className = `p-3 rounded-lg ${role === 'user' ? 'bg-primary-100 dark:bg-primary-900/30 ml-6' : 'bg-gray-100 dark:bg-gray-700 mr-6'}`;
|
||||
if (id) {
|
||||
messageElement.id = id;
|
||||
}
|
||||
|
||||
messageElement.innerHTML = `
|
||||
<div class="flex items-start">
|
||||
<div class="w-8 h-8 rounded-full flex items-center justify-center ${role === 'user' ? 'bg-primary-600' : 'bg-gray-600'} text-white mr-2">
|
||||
<i class="fas ${role === 'user' ? 'fa-user' : 'fa-robot'} text-xs"></i>
|
||||
</div>
|
||||
<div class="flex-1 text-sm ${role === 'user' ? 'text-gray-800 dark:text-gray-200' : 'text-gray-700 dark:text-gray-300'}">
|
||||
${content}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
messagesContainer.appendChild(messageElement);
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisiere den ChatGPT-Assistenten direkt, um sicherzustellen,
|
||||
// dass er auf jeder Seite verfügbar ist, selbst wenn MindMap nicht geladen ist
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Prüfen, ob der Assistent bereits durch MindMap initialisiert wurde
|
||||
if (!window.MindMap || !window.MindMap.assistant) {
|
||||
console.log('KI-Assistent wird direkt initialisiert...');
|
||||
const assistant = new ChatGPTAssistant();
|
||||
assistant.init();
|
||||
|
||||
// Speichere in window.MindMap, falls es existiert, oder erstelle es
|
||||
if (!window.MindMap) {
|
||||
window.MindMap = {};
|
||||
// Initialisiere den ChatGPT-Assistenten direkt
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Prüfen, ob der Assistent bereits durch MindMap initialisiert wurde
|
||||
if (!window.MindMap || !window.MindMap.assistant) {
|
||||
console.log('KI-Assistent wird direkt initialisiert...');
|
||||
const assistant = new ChatGPTAssistant();
|
||||
assistant.init();
|
||||
|
||||
// Speichere in window.MindMap, falls es existiert, oder erstelle es
|
||||
if (!window.MindMap) {
|
||||
window.MindMap = {};
|
||||
}
|
||||
window.MindMap.assistant = assistant;
|
||||
}
|
||||
window.MindMap.assistant = assistant;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Dark/Light-Mode vereinheitlicht -->
|
||||
|
||||
@@ -1,402 +1,232 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Mindmap{% endblock %}
|
||||
{% block title %}Interaktive Mindmap{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* Spezifische Stile für die Mindmap-Seite */
|
||||
/* Grundlegendes Layout */
|
||||
.mindmap-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: calc(100vh - 64px);
|
||||
background: linear-gradient(135deg, #1a1f2e 0%, #0f172a 100%);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Hauptcontainer für die Mindmap */
|
||||
#cy {
|
||||
width: 100%;
|
||||
height: 750px;
|
||||
background-color: var(--bg-secondary);
|
||||
transition: background-color 0.3s ease;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Hintergrundraster für wissenschaftliches Aussehen */
|
||||
#cy::before {
|
||||
content: '';
|
||||
|
||||
/* Header-Bereich */
|
||||
.mindmap-header {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.05) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dark #cy::before {
|
||||
background-image:
|
||||
linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
|
||||
}
|
||||
|
||||
/* Verbesserte Mindmap-Container-Stile */
|
||||
.mindmap-container {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.dark .mindmap-container {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
background-color: var(--bg-secondary);
|
||||
}
|
||||
|
||||
/* Flüssigere Übergänge für Mindmap-Elemente */
|
||||
.mindmap-svg {
|
||||
transition: background-color 0.5s ease;
|
||||
}
|
||||
|
||||
.node {
|
||||
cursor: pointer;
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.node:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.node circle {
|
||||
transition: r 0.3s ease, fill 0.3s ease, stroke 0.3s ease, stroke-width 0.3s ease, filter 0.3s ease;
|
||||
}
|
||||
|
||||
.node text {
|
||||
transition: font-size 0.3s ease, fill 0.3s ease;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.link {
|
||||
transition: stroke 0.3s ease, stroke-width 0.3s ease, opacity 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* Panel-Übergänge für Seitenleiste */
|
||||
[data-sidebar] {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease, display 0s linear 0.3s;
|
||||
}
|
||||
|
||||
[data-sidebar].active {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease, display 0s linear;
|
||||
}
|
||||
|
||||
/* Mindmap-Toolbar mit verbessertem Erscheinungsbild */
|
||||
.mindmap-toolbar {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--bg-secondary);
|
||||
padding: 1.5rem;
|
||||
background: rgba(15, 23, 42, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: background-color 0.3s ease;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
body:not(.dark) .mindmap-toolbar {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
|
||||
.mindmap-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
margin: 0;
|
||||
background: linear-gradient(90deg, #60a5fa, #8b5cf6);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
/* Verbesserte Button-Stile für Mindmap-Toolbar */
|
||||
.mindmap-action-btn {
|
||||
|
||||
/* Kontrollpanel */
|
||||
.control-panel {
|
||||
position: absolute;
|
||||
right: 2rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.control-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
padding: 0.35rem 0.7rem;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 0.25rem;
|
||||
background-color: rgba(79, 70, 229, 0.1);
|
||||
color: #4f46e5;
|
||||
border: 1px solid rgba(79, 70, 229, 0.2);
|
||||
padding: 0.75rem 1rem;
|
||||
margin: 0.5rem 0;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.dark .mindmap-action-btn {
|
||||
background-color: rgba(79, 70, 229, 0.15);
|
||||
border-color: rgba(79, 70, 229, 0.3);
|
||||
}
|
||||
|
||||
.mindmap-action-btn:hover {
|
||||
background-color: rgba(79, 70, 229, 0.2);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.mindmap-action-btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Verbesserte Seitenleisten-Panels mit Animation */
|
||||
.sidebar-panel {
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.sidebar-panel.hidden {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
|
||||
.sidebar-panel.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
/* Info-Panel anpassungen */
|
||||
.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;
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
transform: translateX(-5px);
|
||||
}
|
||||
|
||||
.control-button i {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
/* Info-Panel */
|
||||
.info-panel {
|
||||
position: absolute;
|
||||
left: 2rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
border-radius: 1rem;
|
||||
padding: 1.5rem;
|
||||
width: 300px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
backdrop-filter: blur(4px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.mindmap-info-panel.visible {
|
||||
|
||||
.info-panel.visible {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
pointer-events: auto;
|
||||
transform: translateY(-50%) translateX(0);
|
||||
}
|
||||
|
||||
.info-panel-title {
|
||||
font-size: 1.1rem;
|
||||
|
||||
.info-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #fff;
|
||||
margin-bottom: 1rem;
|
||||
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;
|
||||
|
||||
.info-content {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.node-navigation-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.node-links {
|
||||
|
||||
/* Kategorie-Legende */
|
||||
.category-legend {
|
||||
position: absolute;
|
||||
bottom: 2rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
border-radius: 1rem;
|
||||
padding: 1rem 2rem;
|
||||
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);
|
||||
}
|
||||
|
||||
/* Zusätzliches Layout */
|
||||
.mindmap-section {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.mindmap-section {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.category-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
/* Animationen */
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4 py-8">
|
||||
<div class="flex flex-col lg:flex-row gap-8">
|
||||
<!-- Hauptinhalt -->
|
||||
<div class="w-full lg:w-3/4">
|
||||
<!-- Mindmap-Titelbereich -->
|
||||
<div class="mb-6">
|
||||
<h1 class="text-3xl font-bold mb-2 mystical-glow"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
||||
Wissenslandkarte
|
||||
</h1>
|
||||
<p class="opacity-80 text-lg"
|
||||
x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
Visualisiere die Verbindungen zwischen Gedanken und Konzepten
|
||||
</p>
|
||||
</div>
|
||||
<div class="mindmap-container">
|
||||
<!-- Header -->
|
||||
<div class="mindmap-header">
|
||||
<h1 class="mindmap-title">Interaktive Wissenslandkarte</h1>
|
||||
</div>
|
||||
|
||||
<!-- Mindmap-Container -->
|
||||
<div class="mindmap-container">
|
||||
<!-- Toolbar -->
|
||||
<div class="mindmap-toolbar">
|
||||
<button id="fit-btn" class="mindmap-action-btn" title="An Fenstergröße anpassen">
|
||||
<i class="fa-solid fa-expand"></i>
|
||||
<span>Anpassen</span>
|
||||
</button>
|
||||
<button id="reset-btn" class="mindmap-action-btn" title="Ansicht zurücksetzen">
|
||||
<i class="fa-solid fa-undo"></i>
|
||||
<span>Zurücksetzen</span>
|
||||
</button>
|
||||
<button id="zoom-in-btn" class="mindmap-action-btn" title="Einzoomen">
|
||||
<i class="fa-solid fa-magnifying-glass-plus"></i>
|
||||
<span>Zoom+</span>
|
||||
</button>
|
||||
<button id="zoom-out-btn" class="mindmap-action-btn" title="Auszoomen">
|
||||
<i class="fa-solid fa-magnifying-glass-minus"></i>
|
||||
<span>Zoom-</span>
|
||||
</button>
|
||||
<button id="toggle-labels-btn" class="mindmap-action-btn" title="Labels ein/ausblenden">
|
||||
<i class="fa-solid fa-tags"></i>
|
||||
<span>Labels</span>
|
||||
</button>
|
||||
<button id="fullscreen-btn" class="mindmap-action-btn" title="Vollbildmodus">
|
||||
<i class="fa-solid fa-expand"></i>
|
||||
<span>Vollbild</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Hauptvisualisierung -->
|
||||
<div id="cy"></div>
|
||||
|
||||
<!-- Hauptvisualisierung -->
|
||||
<div id="cy"></div>
|
||||
<!-- Kontrollpanel -->
|
||||
<div class="control-panel">
|
||||
<button id="zoomIn" class="control-button">
|
||||
<i class="fas fa-search-plus"></i>
|
||||
<span>Vergrößern</span>
|
||||
</button>
|
||||
<button id="zoomOut" class="control-button">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
<span>Verkleinern</span>
|
||||
</button>
|
||||
<button id="resetView" class="control-button">
|
||||
<i class="fas fa-sync"></i>
|
||||
<span>Zurücksetzen</span>
|
||||
</button>
|
||||
<button id="toggleLegend" class="control-button">
|
||||
<i class="fas fa-layer-group"></i>
|
||||
<span>Legende</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Info-Panel -->
|
||||
<div id="node-info-panel" class="mindmap-info-panel">
|
||||
<h4 class="info-panel-title">Knoteninfo</h4>
|
||||
<p id="node-description" class="info-panel-description">Wählen Sie einen Knoten aus...</p>
|
||||
|
||||
<div class="node-navigation">
|
||||
<h5 class="node-navigation-title">Verknüpfte Knoten</h5>
|
||||
<div id="connected-nodes" class="node-links">
|
||||
<!-- Wird dynamisch befüllt -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Info-Panel -->
|
||||
<div id="infoPanel" class="info-panel">
|
||||
<h3 class="info-title">Knotendetails</h3>
|
||||
<div class="info-content"></div>
|
||||
</div>
|
||||
|
||||
<!-- Kategorie-Legende -->
|
||||
<div id="categoryLegend" class="category-legend">
|
||||
<div class="category-item">
|
||||
<div class="category-color" style="background-color: #60a5fa;"></div>
|
||||
<span>Philosophie</span>
|
||||
</div>
|
||||
|
||||
<!-- Seitenleiste -->
|
||||
<div class="w-full lg:w-1/4 space-y-6">
|
||||
<!-- Nutzlänge -->
|
||||
<div class="p-5 rounded-lg overflow-hidden border transition-colors duration-300"
|
||||
x-bind:class="darkMode ? 'bg-slate-800/40 border-slate-700/50' : 'bg-white border-slate-200'"
|
||||
data-sidebar="about-mindmap">
|
||||
<h3 class="text-xl font-semibold mb-3"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
||||
<i class="fa-solid fa-circle-info text-purple-400 mr-2"></i>Über die Mindmap
|
||||
</h3>
|
||||
<div x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
<p class="mb-2">Die interaktive Wissenslandkarte zeigt Verbindungen zwischen verschiedenen Gedanken und Konzepten.</p>
|
||||
<p class="mb-2">Sie können:</p>
|
||||
<ul class="list-disc pl-5 space-y-1 text-sm">
|
||||
<li>Knoten auswählen, um Details zu sehen</li>
|
||||
<li>Zoomen (Mausrad oder Pinch-Geste)</li>
|
||||
<li>Die Karte verschieben (Drag & Drop)</li>
|
||||
<li>Die Toolbar nutzen für weitere Aktionen</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kategorienlegende -->
|
||||
<div class="p-5 rounded-lg overflow-hidden border transition-colors duration-300"
|
||||
x-bind:class="darkMode ? 'bg-slate-800/40 border-slate-700/50' : 'bg-white border-slate-200'"
|
||||
data-sidebar="categories">
|
||||
<h3 class="text-xl font-semibold mb-3"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
||||
<i class="fa-solid fa-palette text-purple-400 mr-2"></i>Kategorien
|
||||
</h3>
|
||||
<div id="category-legend" class="space-y-2 text-sm"
|
||||
x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
<!-- Wird dynamisch befüllt -->
|
||||
<div class="flex items-center"><span class="w-3 h-3 rounded-full bg-indigo-600 mr-2"></span> Philosophie</div>
|
||||
<div class="flex items-center"><span class="w-3 h-3 rounded-full bg-emerald-600 mr-2"></span> Wissenschaft</div>
|
||||
<div class="flex items-center"><span class="w-3 h-3 rounded-full bg-indigo-500 mr-2"></span> Technologie</div>
|
||||
<div class="flex items-center"><span class="w-3 h-3 rounded-full bg-violet-500 mr-2"></span> Künste</div>
|
||||
<div class="flex items-center"><span class="w-3 h-3 rounded-full bg-cyan-700 mr-2"></span> Psychologie</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Knotenbeschreibung (anfangs ausgeblendet) -->
|
||||
<div class="p-5 rounded-lg overflow-hidden border transition-colors duration-300 hidden"
|
||||
x-bind:class="darkMode ? 'bg-slate-800/40 border-slate-700/50' : 'bg-white border-slate-200'"
|
||||
data-sidebar="node-description">
|
||||
<h3 class="text-xl font-semibold mb-3"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'"
|
||||
data-node-title>Knotenbeschreibung</h3>
|
||||
<div class="space-y-3" x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
<p class="text-sm leading-relaxed" data-node-description>
|
||||
Wählen Sie einen Knoten in der Mindmap aus, um dessen Beschreibung hier anzuzeigen.
|
||||
</p>
|
||||
<div class="pt-2 mt-2 border-t" x-bind:class="darkMode ? 'border-slate-700/50' : 'border-slate-200'">
|
||||
<button class="text-xs px-3 py-1.5 rounded bg-indigo-100 text-indigo-700 hover:bg-indigo-200 transition-colors dark:bg-indigo-900/30 dark:text-indigo-300 dark:hover:bg-indigo-800/40"
|
||||
onclick="window.mindmapInstance && window.mindmapInstance.selectedNode && window.mindmapInstance.centerNodeInView(window.mindmapInstance.selectedNode)">
|
||||
<i class="fa-solid fa-crosshairs mr-1"></i> Knoten zentrieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Meine Mindmaps -->
|
||||
{% if current_user.is_authenticated %}
|
||||
<div class="p-5 rounded-lg overflow-hidden border transition-colors duration-300"
|
||||
x-bind:class="darkMode ? 'bg-slate-800/40 border-slate-700/50' : 'bg-white border-slate-200'">
|
||||
<h3 class="text-xl font-semibold mb-3"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
||||
<i class="fa-solid fa-map text-purple-400 mr-2"></i>Meine Mindmaps
|
||||
</h3>
|
||||
<div class="mb-3">
|
||||
<a href="{{ url_for('create_mindmap') }}" class="w-full inline-block py-2 px-4 bg-purple-600 hover:bg-purple-700 text-white rounded-lg text-center text-sm font-medium transition-colors">
|
||||
<i class="fa-solid fa-plus mr-1"></i> Neue Mindmap erstellen
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 max-h-60 overflow-y-auto"
|
||||
x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
{% if user_mindmaps %}
|
||||
{% for mindmap in user_mindmaps %}
|
||||
<a href="{{ url_for('user_mindmap', mindmap_id=mindmap.id) }}" class="block p-2 hover:bg-purple-500/20 rounded-lg transition-colors">
|
||||
<div class="text-sm font-medium">{{ mindmap.name }}</div>
|
||||
<div class="text-xs opacity-70">{{ mindmap.nodes|length }} Knoten</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-sm italic">Sie haben noch keine eigenen Mindmaps erstellt.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="category-item">
|
||||
<div class="category-color" style="background-color: #8b5cf6;"></div>
|
||||
<span>Wissenschaft</span>
|
||||
</div>
|
||||
<div class="category-item">
|
||||
<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>
|
||||
<span>Künste</span>
|
||||
</div>
|
||||
<div class="category-item">
|
||||
<div class="category-color" style="background-color: #ef4444;"></div>
|
||||
<span>Psychologie</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<!-- Cytoscape -->
|
||||
<script src="{{ url_for('static', filename='js/cytoscape.min.js') }}"></script>
|
||||
<!-- Mindmap initialisierung -->
|
||||
<script src="{{ url_for('static', filename='js/mindmap-init.js') }}"></script>
|
||||
<!-- Update Mindmap mit wissenschaftlichen Knoten -->
|
||||
<!-- Cytoscape und Erweiterungen -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape-cose-bilkent/4.1.0/cytoscape-cose-bilkent.min.js"></script>
|
||||
|
||||
<!-- Unsere JavaScript-Dateien -->
|
||||
<script src="{{ url_for('static', filename='js/update_mindmap.js') }}"></script>
|
||||
<!-- Verbesserte Interaktion -->
|
||||
<script src="{{ url_for('static', filename='js/mindmap-interaction.js') }}"></script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user