Files
website/static/js/modules/mindmap-page.js

456 lines
16 KiB
JavaScript

/**
* Mindmap-Seite JavaScript
* Spezifische Funktionen für die Mindmap-Seite
*/
// Füge das Modul zum globalen MindMap-Objekt hinzu
if (!window.MindMap) {
window.MindMap = {};
}
// Registriere den Initialisierer im MindMap-Objekt
if (window.MindMap) {
window.MindMap.pageInitializers = window.MindMap.pageInitializers || {};
window.MindMap.pageInitializers.mindmap = initMindmapPage;
}
// Initialisiere die Mindmap-Seite nur, wenn alle Abhängigkeiten vorhanden sind
if (window.MindMap && typeof MindMapVisualization !== 'undefined') {
if (document.body && document.body.dataset && document.body.dataset.page === 'mindmap') {
window.MindMap.pageInitializers = window.MindMap.pageInitializers || {};
window.MindMap.pageInitializers.mindmap = initMindmapPage;
initMindmapPage();
}
}
document.addEventListener('DOMContentLoaded', function() {
// Prüfe, ob wir auf der Mindmap-Seite sind und initialisiere
if (document.body && document.body.dataset && document.body.dataset.page === 'mindmap') {
initMindmapPage();
}
});
/**
* Initialisiert die Mindmap-Seite
*/
function initMindmapPage() {
console.log('Mindmap-Seite Initialisierung startet...');
console.log('D3 Bibliothek verfügbar:', typeof d3 !== 'undefined');
console.log('MindMapVisualization verfügbar:', typeof MindMapVisualization !== 'undefined');
const mindmapContainer = document.getElementById('cy');
const thoughtsContainer = document.getElementById('thoughts-container');
const nodeDetailsContainer = document.getElementById('node-details');
if (!mindmapContainer) {
console.error('Mindmap-Container nicht gefunden!');
return;
}
console.log('Mindmap-Container gefunden:', mindmapContainer);
// Prüfe, ob D3.js geladen ist
if (typeof d3 === 'undefined') {
console.error('D3.js ist nicht geladen!');
if (mindmapContainer) {
mindmapContainer.innerHTML = `
<div class="glass-effect p-6 text-center">
<div class="text-red-500 mb-4">
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
</div>
<p class="text-xl">D3.js konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
</div>
`;
}
return;
}
// Prüfe, ob MindMapVisualization definiert ist
if (typeof MindMapVisualization === 'undefined') {
console.error('MindMapVisualization-Klasse ist nicht definiert!');
if (mindmapContainer) {
mindmapContainer.innerHTML = `
<div class="glass-effect p-6 text-center">
<div class="text-red-500 mb-4">
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
</div>
<p class="text-xl">MindMap-Visualisierung konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
</div>
`;
}
return;
}
// Erstelle die Mindmap-Visualisierung
let mindmap;
try {
console.log('Versuche, MindMapVisualization zu erstellen...');
mindmap = new MindMapVisualization('#cy', {
height: 600,
onNodeClick: handleNodeClick
});
// Globale Referenz für die Zoom-Buttons erstellen
window.mindmapInstance = mindmap;
// Lade die Mindmap-Daten
mindmap.loadData();
console.log('MindMapVisualization erfolgreich erstellt und geladen');
// Dark Mode Listener registrieren
registerDarkModeListener(mindmap);
} catch (error) {
console.error('Fehler beim Erstellen der MindMapVisualization:', error);
if (mindmapContainer) {
mindmapContainer.innerHTML = `
<div class="glass-effect p-6 text-center">
<div class="text-red-500 mb-4">
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
</div>
<p class="text-xl">Fehler beim Erstellen der Mindmap-Visualisierung:</p>
<p class="text-md mt-2">${error.message}</p>
</div>
`;
}
return;
}
// Suchfunktion für die Mindmap
const searchInput = document.getElementById('search-mindmap');
if (searchInput) {
searchInput.addEventListener('input', function(e) {
mindmap.filterBySearchTerm(e.target.value);
});
}
// UI-Elemente initialisieren
initializeMindmapButtons(mindmap);
/**
* Behandelt Klicks auf Mindmap-Knoten
*/
async function handleNodeClick(node) {
// Details im Detailbereich anzeigen
if (nodeDetailsContainer) {
updateNodeDetails(node);
}
if (!thoughtsContainer) return;
// Zeige Lade-Animation
thoughtsContainer.innerHTML = `
<div class="flex justify-center items-center p-4">
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary-500"></div>
</div>
`;
try {
// Lade Gedanken für den ausgewählten Knoten
const response = await fetch(`/api/nodes/${node.id}/thoughts`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const thoughts = await response.json();
// Gedanken anzeigen
renderThoughts(thoughts, node.name);
} catch (error) {
console.error('Fehler beim Laden der Gedanken:', error);
thoughtsContainer.innerHTML = `
<div class="p-4">
<div class="text-red-500 mb-2">
<i class="fa-solid fa-triangle-exclamation text-xl"></i>
</div>
<p class="text-sm">Fehler beim Laden der Gedanken.</p>
</div>
`;
}
}
/**
* Aktualisiert den Node-Details-Bereich mit Informationen zum ausgewählten Knoten
*/
function updateNodeDetails(node) {
if (!nodeDetailsContainer) return;
const isDarkMode = document.documentElement.classList.contains('dark');
const textClass = isDarkMode ? 'text-gray-300' : 'text-gray-600';
nodeDetailsContainer.innerHTML = `
<div>
<h3 class="text-lg font-semibold mb-2 ${isDarkMode ? 'text-white' : 'text-gray-800'}">
${node.name}
</h3>
<div class="mb-3">
<span class="px-2 py-1 text-xs rounded-full ${getCategoryBadgeClass(node.category)}">
${node.category || 'Keine Kategorie'}
</span>
</div>
<p class="text-sm ${textClass}">
${node.description || 'Keine Beschreibung verfügbar.'}
</p>
<div class="mt-4 text-sm">
<div class="flex justify-between items-center mb-1">
<span class="${textClass}">Erstellt:</span>
<span class="${textClass}">${formatDate(node.created_at || new Date())}</span>
</div>
<div class="flex justify-between items-center">
<span class="${textClass}">Verbindungen:</span>
<span class="${textClass}">${node.connections || 0}</span>
</div>
</div>
</div>
`;
}
/**
* Formatiert ein Datum in ein lesbares Format
*/
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('de-DE', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
}
/**
* Liefert eine CSS-Klasse für die Kategorie-Badge basierend auf der Kategorie
*/
function getCategoryBadgeClass(category) {
if (!category) return 'bg-gray-500 text-white';
const categories = {
'Konzept': 'bg-blue-500 text-white',
'Theorie': 'bg-purple-500 text-white',
'Methode': 'bg-green-500 text-white',
'Person': 'bg-yellow-500 text-black',
'Ereignis': 'bg-red-500 text-white',
'Referenz': 'bg-indigo-500 text-white'
};
return categories[category] || 'bg-gray-500 text-white';
}
/**
* Rendert die Gedanken in den Container
*/
function renderThoughts(thoughts, nodeName) {
const isDarkMode = document.documentElement.classList.contains('dark');
// Wenn keine Gedanken vorhanden sind
if (thoughts.length === 0) {
thoughtsContainer.innerHTML = `
<div class="text-center py-4">
<div class="text-blue-400 mb-2">
<i class="fa-solid fa-info-circle text-xl"></i>
</div>
<p class="text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}">
Keine Gedanken zu "${nodeName}" vorhanden.
</p>
<button id="add-thought-btn" class="mt-3 px-3 py-1.5 text-xs rounded-lg transition-colors duration-200"
${isDarkMode ?
'class="bg-primary-600 hover:bg-primary-700 text-white"' :
'class="bg-primary-500 hover:bg-primary-600 text-white"'}>
<i class="fa-solid fa-plus mr-1"></i> Gedanke hinzufügen
</button>
</div>
`;
// Event-Listener für den Button
document.getElementById('add-thought-btn')?.addEventListener('click', () => {
openAddThoughtModal(nodeName);
});
return;
}
// Gedanken anzeigen
thoughtsContainer.innerHTML = `
<div class="flex justify-between items-center mb-3">
<h3 class="text-sm font-semibold ${isDarkMode ? 'text-white' : 'text-gray-800'}">
Gedanken zu "${nodeName}"
</h3>
<button id="add-thought-btn" class="px-2 py-1 text-xs rounded-lg transition-colors duration-200"
${isDarkMode ?
'class="bg-primary-600 hover:bg-primary-700 text-white"' :
'class="bg-primary-500 hover:bg-primary-600 text-white"'}>
<i class="fa-solid fa-plus mr-1"></i> Neu
</button>
</div>
<div class="space-y-3 max-h-80 overflow-y-auto pr-1" id="thoughts-grid"></div>
`;
// Button-Event-Listener
document.getElementById('add-thought-btn')?.addEventListener('click', () => {
openAddThoughtModal(nodeName);
});
// Gedanken-Karten rendern
const thoughtsGrid = document.getElementById('thoughts-grid');
thoughts.forEach((thought, index) => {
const card = createThoughtCard(thought, isDarkMode);
// Animation verzögern für gestaffeltes Erscheinen
setTimeout(() => {
card.classList.add('opacity-100');
card.classList.remove('opacity-0', 'translate-y-4');
}, index * 100);
thoughtsGrid.appendChild(card);
});
}
/**
* Erstellt eine Gedanken-Karte
*/
function createThoughtCard(thought, isDarkMode) {
const card = document.createElement('div');
card.className = `relative transition-all duration-300 opacity-0 translate-y-4 transform rounded-lg overflow-hidden p-3 border-l-4 ${isDarkMode ? 'bg-gray-800/70' : 'bg-white/90'} border-l-[${thought.color_code || '#4080ff'}]`;
// Karten-Inhalt
card.innerHTML = `
<div class="flex justify-between items-start mb-1">
<h4 class="text-sm font-semibold ${isDarkMode ? 'text-white' : 'text-gray-800'}">${thought.title}</h4>
<div class="text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}">${formatDate(thought.timestamp)}</div>
</div>
<div class="prose dark:prose-invert prose-sm">
<p class="text-xs line-clamp-2 ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}">${thought.content}</p>
</div>
${thought.keywords ? `
<div class="flex flex-wrap gap-1 mt-2">
${thought.keywords.split(',').slice(0, 2).map(keyword =>
`<span class="px-2 py-0.5 text-[10px] rounded-full ${isDarkMode ? 'bg-gray-700 text-gray-300' : 'bg-gray-200 text-gray-700'}">${keyword.trim()}</span>`
).join('')}
${thought.keywords.split(',').length > 2 ?
`<span class="px-2 py-0.5 text-[10px] rounded-full ${isDarkMode ? 'bg-gray-700 text-gray-300' : 'bg-gray-200 text-gray-700'}">+${thought.keywords.split(',').length - 2}</span>` :
''}
</div>
` : ''}
<div class="mt-2 flex justify-between items-center">
<div class="text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}">
<i class="fa-solid fa-user text-xs mr-1"></i> ${thought.author}
</div>
<button class="viewThought-btn text-xs px-2 py-1 rounded ${isDarkMode ? 'hover:bg-white/10' : 'hover:bg-gray-200'} transition-colors"
data-thought-id="${thought.id}">
<i class="fa-solid fa-eye mr-1"></i> Ansehen
</button>
</div>
`;
// Event-Listener für die Detailansicht
card.querySelector('.viewThought-btn').addEventListener('click', function() {
const thoughtId = this.getAttribute('data-thought-id');
showThoughtDetail(thoughtId);
});
return card;
}
/**
* Event-Listener für Dark-Mode-Änderungen registrieren
*/
function registerDarkModeListener(mindmap) {
document.addEventListener('darkModeToggled', function(event) {
const isDark = event.detail.isDark;
console.log('Dark mode changed to:', isDark);
// Mindmap-Styling aktualisieren
if (mindmap && mindmap.updateColorScheme) {
mindmap.updateColorScheme(isDark);
}
// UI-Elemente aktualisieren, falls notwendig
if (nodeDetailsContainer) {
const selectedNode = mindmap.getSelectedNode();
if (selectedNode) {
updateNodeDetails(selectedNode);
}
}
if (thoughtsContainer) {
const thoughts = thoughtsContainer.querySelector('#thoughts-grid');
if (thoughts) {
// Einfache Aktualisierung - im Produktionscode würde man das komplexer machen
const selectedNode = mindmap.getSelectedNode();
if (selectedNode) {
handleNodeClick(selectedNode);
}
}
}
});
}
/**
* Initialisiert die Buttons für die Mindmap
*/
function initializeMindmapButtons(mindmap) {
// Zoom-Buttons
document.getElementById('zoomIn')?.addEventListener('click', () => mindmap.zoomIn());
document.getElementById('zoomOut')?.addEventListener('click', () => mindmap.zoomOut());
document.getElementById('zoomReset')?.addEventListener('click', () => mindmap.zoomReset());
// Layout-Button
document.getElementById('reLayout')?.addEventListener('click', () => mindmap.reLayout());
// Export-Button
document.getElementById('exportMindmap')?.addEventListener('click', () => mindmap.exportMindmap());
// Bearbeitungs-Buttons
document.getElementById('addNode')?.addEventListener('click', () => openAddNodeModal(mindmap));
document.getElementById('addEdge')?.addEventListener('click', () => mindmap.startAddEdgeMode());
document.getElementById('editNode')?.addEventListener('click', () => {
const selectedNode = mindmap.getSelectedNode();
if (selectedNode) {
openEditNodeModal(selectedNode, mindmap);
} else {
showNotification('Bitte wählen Sie zuerst einen Knoten aus.', 'warning');
}
});
document.getElementById('deleteNode')?.addEventListener('click', () => {
const selectedNode = mindmap.getSelectedNode();
if (selectedNode) {
confirmDeleteNode(selectedNode, mindmap);
} else {
showNotification('Bitte wählen Sie zuerst einen Knoten aus.', 'warning');
}
});
document.getElementById('deleteEdge')?.addEventListener('click', () => {
const selectedEdge = mindmap.getSelectedEdge();
if (selectedEdge) {
confirmDeleteEdge(selectedEdge, mindmap);
} else {
showNotification('Bitte wählen Sie zuerst eine Verbindung aus.', 'warning');
}
});
}
/**
* Zeigt eine Benachrichtigung an
*/
function showNotification(message, type = 'info') {
console.log(`Benachrichtigung (${type}): ${message}`);
// Implementiere eine Toast-Benachrichtigung
// ...
}
// ... Weitere Funktionen wie openAddThoughtModal, openAddNodeModal usw. ...
}
/**
* Öffnet das Modal zum Hinzufügen eines Gedankens
*/
function openAddThoughtModal(nodeName) {
// Modal-Implementierung...
}
/**
* Zeigt die Detailansicht eines Gedankens
*/
function showThoughtDetail(thoughtId) {
// Detailansicht-Implementierung...
}