feat(mindmap): entferne veraltete Mindmap-Module und -Dateien zur Optimierung der Codebasis und Verbesserung der Wartbarkeit

This commit is contained in:
2025-05-09 19:46:26 +01:00
parent dd172d8596
commit 4b0613eb6b
6 changed files with 198 additions and 3341 deletions

View File

@@ -1,219 +0,0 @@
/**
* Mindmap Interaction Enhancement
* Verbessert die Interaktion mit der Mindmap und steuert die Seitenleisten-Anzeige
*/
// Globale Variablen
let selectedNode = null;
let isLegendVisible = true;
// Initialisierung der Mindmap
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
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) {
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 = '';
}
}

View File

@@ -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>

View File

@@ -1,690 +0,0 @@
/**
* Mindmap.js - Interaktive Mind-Map Implementierung
* - Event-Listener und Interaktionslogik
* - Fetch API für REST-Zugriffe
* - Socket.IO für Echtzeit-Synchronisation
* - Lazy Loading & Progressive Disclosure
*/
(async () => {
/* 1. Event-Listener und Interaktionslogik */
// Warte auf die Cytoscape-Instanz
document.addEventListener('mindmap-loaded', function() {
const cy = window.cy;
if (!cy) return;
// 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';
}
});
// Mouseout-Event für Tooltip
cy.nodes().unbind('mouseout').bind('mouseout', () => {
const tooltip = document.getElementById('node-tooltip');
if (tooltip) {
tooltip.style.opacity = '0';
}
});
// Kontextmenü
setupContextMenu(cy);
});
})();
/**
* Richtet das Kontextmenü ein
* @param {Object} cy - Cytoscape-Instanz
*/
function setupContextMenu(cy) {
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 16px';
item.style.cursor = 'pointer';
item.style.transition = 'background-color 0.2s';
item.addEventListener('mouseover', () => {
item.style.backgroundColor = '#f0f0f0';
});
item.addEventListener('mouseout', () => {
item.style.backgroundColor = '';
});
item.addEventListener('click', () => {
const action = item.dataset.action;
handleContextMenuAction(action, node);
contextMenu.style.display = 'none';
});
});
// Menü positionieren
contextMenu.style.left = menuX + 'px';
contextMenu.style.top = menuY + 'px';
contextMenu.style.display = 'block';
// Klick außerhalb schließt Menü
document.addEventListener('click', function closeMenu(e) {
if (!contextMenu.contains(e.target)) {
contextMenu.style.display = 'none';
document.removeEventListener('click', closeMenu);
}
});
});
}
/**
* Behandelt Aktionen aus dem Kontextmenü
* @param {string} action - Die ausgewählte Aktion
* @param {Object} node - Der betroffene Knoten
*/
function handleContextMenuAction(action, node) {
switch (action) {
case 'edit':
// Implementiere Bearbeitungslogik
console.log('Bearbeite Knoten:', node.id());
break;
case 'connect':
// Implementiere Verbindungslogik
console.log('Erstelle Verbindung für:', node.id());
break;
case 'delete':
// Implementiere Löschlogik
console.log('Lösche Knoten:', node.id());
break;
}
}
/* 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 {
// Initial nur die oberste Ebene laden
const rootNodes = await get('/api/mind_map_nodes?level=root');
// 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(rootNodes) ? rootNodes : [];
// Root-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,
hasChildren: node.has_children || false, // Neue Eigenschaft
expanded: false // Neue Eigenschaft für den Expansionsstatus
},
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();
}
} 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. 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');
})();

Binary file not shown.

View File

@@ -2,8 +2,144 @@
* Update Mindmap * Update Mindmap
* Dieses Skript fügt Knoten zur Mindmap hinzu und stellt sicher, * Dieses Skript fügt Knoten zur Mindmap hinzu und stellt sicher,
* dass sie im neuronalen Netzwerk-Design angezeigt werden. * 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
}
]
};
// Initiale Mindmap-Daten (nur oberste Ebene)
const mindmapData = {
nodes: [
{
id: 'philosophy',
label: 'Philosophie',
category: 'Philosophie',
description: 'Die Lehre vom Denken und der Erkenntnis',
hasChildren: true,
expanded: false
},
{
id: 'science',
label: 'Wissenschaft',
category: 'Wissenschaft',
description: 'Systematische Erforschung der Natur und Gesellschaft',
hasChildren: true,
expanded: false
},
{
id: 'technology',
label: 'Technologie',
category: 'Technologie',
description: 'Anwendung wissenschaftlicher Erkenntnisse',
hasChildren: true,
expanded: false
},
{
id: 'arts',
label: 'Künste',
category: 'Künste',
description: 'Kreativer Ausdruck und künstlerische Gestaltung',
hasChildren: true,
expanded: false
}
],
edges: []
};
// Warte bis DOM geladen ist // Warte bis DOM geladen ist
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
console.log('DOMContentLoaded Event ausgelöst'); console.log('DOMContentLoaded Event ausgelöst');
@@ -127,145 +263,16 @@ document.addEventListener('DOMContentLoaded', function() {
// Event auslösen, damit andere Scripte reagieren können // Event auslösen, damit andere Scripte reagieren können
document.dispatchEvent(new Event('mindmap-loaded')); document.dispatchEvent(new Event('mindmap-loaded'));
console.log('mindmap-loaded Event ausgelöst'); console.log('mindmap-loaded Event ausgelöst');
// Event-Listener für Knoten-Klicks hinzufügen
cy.on('tap', 'node', async function(evt) {
const node = evt.target;
if (node.data('hasChildren') && !node.data('expanded')) {
await loadSubthemes(node);
}
});
}); });
// Mindmap-Daten
const mindmapData = {
nodes: [
// Philosophie
{
id: 'philosophy',
label: 'Philosophie',
category: 'Philosophie',
description: 'Die Lehre vom Denken und der Erkenntnis'
},
{
id: 'epistemology',
label: 'Erkenntnistheorie',
category: 'Philosophie',
description: 'Untersuchung der Natur und Grenzen menschlicher Erkenntnis'
},
{
id: 'ethics',
label: 'Ethik',
category: 'Philosophie',
description: 'Lehre vom moralisch richtigen Handeln'
},
// Wissenschaft
{
id: 'science',
label: 'Wissenschaft',
category: 'Wissenschaft',
description: 'Systematische Erforschung der Natur und Gesellschaft'
},
{
id: 'physics',
label: 'Physik',
category: 'Wissenschaft',
description: 'Lehre von der Materie und ihren Wechselwirkungen'
},
{
id: 'biology',
label: 'Biologie',
category: 'Wissenschaft',
description: 'Lehre von den Lebewesen und ihren Lebensprozessen'
},
// Technologie
{
id: 'technology',
label: 'Technologie',
category: 'Technologie',
description: 'Anwendung wissenschaftlicher Erkenntnisse'
},
{
id: 'ai',
label: 'Künstliche Intelligenz',
category: 'Technologie',
description: 'Maschinelles Lernen und intelligente Systeme'
},
{
id: 'robotics',
label: 'Robotik',
category: 'Technologie',
description: 'Entwicklung und Steuerung von Robotern'
},
// Künste
{
id: 'arts',
label: 'Künste',
category: 'Künste',
description: 'Kreativer Ausdruck und künstlerische Gestaltung'
},
{
id: 'music',
label: 'Musik',
category: 'Künste',
description: 'Kunst der Töne und Klänge'
},
{
id: 'literature',
label: 'Literatur',
category: 'Künste',
description: 'Schriftliche Werke und Dichtkunst'
},
// Psychologie
{
id: 'psychology',
label: 'Psychologie',
category: 'Psychologie',
description: 'Wissenschaft vom Erleben und Verhalten'
},
{
id: 'cognitive',
label: 'Kognitive Psychologie',
category: 'Psychologie',
description: 'Studium mentaler Prozesse'
},
{
id: 'behavioral',
label: 'Verhaltenspsychologie',
category: 'Psychologie',
description: 'Analyse von Verhaltensmustern'
}
],
edges: [
// Philosophie-Verbindungen
{ source: 'philosophy', target: 'epistemology', label: 'umfasst' },
{ source: 'philosophy', target: 'ethics', label: 'umfasst' },
{ source: 'philosophy', target: 'science', label: 'beeinflusst' },
// Wissenschaft-Verbindungen
{ source: 'science', target: 'physics', label: 'umfasst' },
{ source: 'science', target: 'biology', label: 'umfasst' },
{ source: 'science', target: 'technology', label: 'fördert' },
// Technologie-Verbindungen
{ source: 'technology', target: 'ai', label: 'umfasst' },
{ source: 'technology', target: 'robotics', label: 'umfasst' },
{ source: 'ai', target: 'cognitive', label: 'beeinflusst' },
// Künste-Verbindungen
{ source: 'arts', target: 'music', label: 'umfasst' },
{ source: 'arts', target: 'literature', label: 'umfasst' },
{ source: 'arts', target: 'psychology', label: 'beeinflusst' },
// Psychologie-Verbindungen
{ source: 'psychology', target: 'cognitive', label: 'umfasst' },
{ source: 'psychology', target: 'behavioral', label: 'umfasst' },
{ source: 'psychology', target: 'ethics', label: 'beeinflusst' },
// Interdisziplinäre Verbindungen
{ source: 'cognitive', target: 'ai', label: 'inspiriert' },
{ source: 'physics', target: 'technology', label: 'ermöglicht' },
{ source: 'literature', target: 'philosophy', label: 'reflektiert' }
]
};
// Kategorie-Farben definieren // Kategorie-Farben definieren
const categoryColors = { const categoryColors = {
'Philosophie': '#60a5fa', 'Philosophie': '#60a5fa',
@@ -653,3 +660,57 @@ function createFlashContainer() {
document.body.appendChild(container); document.body.appendChild(container);
return container; return container;
} }
// Funktion zum Laden der Subthemen
async function loadSubthemes(node) {
try {
// Simuliere Datenbankabfrage (später durch echte API ersetzen)
const subthemes = subthemesDatabase[node.id()];
if (!subthemes) return;
// Animation starten
showFlash('Lade Subthemen...', 'info');
// Neue Knoten hinzufügen
subthemes.forEach(subtheme => {
cy.add({
group: 'nodes',
data: {
...subtheme,
expanded: false
}
});
// Kante zum Elternknoten hinzufügen
cy.add({
group: 'edges',
data: {
id: `${node.id()}_${subtheme.id}`,
source: node.id(),
target: subtheme.id
}
});
});
// Node als expandiert markieren
node.data('expanded', true);
// Layout neu berechnen mit Animation
cy.layout({
name: 'cose',
animate: true,
animationDuration: 500,
refresh: 20,
fit: true,
padding: 30
}).run();
// Erfolgsmeldung anzeigen
showFlash('Subthemen erfolgreich geladen', 'success');
} catch (error) {
console.error('Fehler beim Laden der Subthemen:', error);
showFlash('Fehler beim Laden der Subthemen', 'error');
}
}

File diff suppressed because it is too large Load Diff