456 lines
16 KiB
JavaScript
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...
|
|
}
|