✨ feat(mindmap): entferne veraltete Mindmap-Module und -Dateien zur Optimierung der Codebasis und Verbesserung der Wartbarkeit
This commit is contained in:
@@ -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 = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,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.
@@ -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,144 +263,15 @@ 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');
|
||||||
});
|
|
||||||
|
|
||||||
// Mindmap-Daten
|
// Event-Listener für Knoten-Klicks hinzufügen
|
||||||
const mindmapData = {
|
cy.on('tap', 'node', async function(evt) {
|
||||||
nodes: [
|
const node = evt.target;
|
||||||
// Philosophie
|
if (node.data('hasChildren') && !node.data('expanded')) {
|
||||||
{
|
await loadSubthemes(node);
|
||||||
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 = {
|
||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
2061
static/mindmap.js
2061
static/mindmap.js
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user