chore: Änderungen commited
This commit is contained in:
18
app.py
18
app.py
@@ -1154,6 +1154,24 @@ def remove_node_from_mindmap(mindmap_id, node_id):
|
|||||||
|
|
||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
|
|
||||||
|
@app.route('/api/mindmap/node/<int:node_id>', methods=['GET'])
|
||||||
|
@login_required
|
||||||
|
def get_node_info(node_id):
|
||||||
|
"""Liefert Detailinformationen zu einem einzelnen Knoten"""
|
||||||
|
try:
|
||||||
|
# Knoten abrufen
|
||||||
|
node = MindMapNode.query.get_or_404(node_id)
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'id': node.id,
|
||||||
|
'name': node.name,
|
||||||
|
'description': node.description or '',
|
||||||
|
'color_code': node.color_code or '#9F7AEA'
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Fehler in get_node_info: {str(e)}")
|
||||||
|
return jsonify({'success': False, 'message': str(e)}), 500
|
||||||
|
|
||||||
@app.route('/api/mindmap/<int:mindmap_id>/update_node_position', methods=['POST'])
|
@app.route('/api/mindmap/<int:mindmap_id>/update_node_position', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def update_node_position(mindmap_id):
|
def update_node_position(mindmap_id):
|
||||||
|
|||||||
@@ -319,6 +319,10 @@
|
|||||||
<i class="fas fa-save"></i>
|
<i class="fas fa-save"></i>
|
||||||
<span>Layout speichern</span>
|
<span>Layout speichern</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button id="toggle-public-mindmap-btn" class="mindmap-action-btn">
|
||||||
|
<i class="fas fa-globe"></i>
|
||||||
|
<span>Öffentliche Mindmap</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mindmap Container mit Positionsindikator -->
|
<!-- Mindmap Container mit Positionsindikator -->
|
||||||
@@ -538,13 +542,415 @@
|
|||||||
cy.style().selector('node').style({'text-opacity': labelsVisible ? 1 : 0}).update();
|
cy.style().selector('node').style({'text-opacity': labelsVisible ? 1 : 0}).update();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: add-note-btn und save-layout-btn Funktionalität (benötigt /edit_mindmap Seite oder API Endpunkte)
|
// Notizfunktion implementieren
|
||||||
document.getElementById('add-note-btn').addEventListener('click', function() {
|
document.getElementById('add-note-btn').addEventListener('click', function() {
|
||||||
showUINotification('Notizfunktion wird auf der Bearbeitungsseite implementiert.', 'info');
|
const selectedNodes = cy.$('node:selected');
|
||||||
});
|
if (selectedNodes.length === 0) {
|
||||||
document.getElementById('save-layout-btn').addEventListener('click', function() {
|
showUINotification('Bitte wählen Sie einen Knoten aus, um eine Notiz hinzuzufügen.', 'info');
|
||||||
showUINotification('Layout-Speicherung wird auf der Bearbeitungsseite implementiert.', 'info');
|
return;
|
||||||
});
|
}
|
||||||
|
|
||||||
|
const nodeId = selectedNodes[0].id();
|
||||||
|
|
||||||
|
// Notizen für den ausgewählten Knoten laden
|
||||||
|
fetch(`/api/mindmap/node/${nodeId}/notes`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
// Notiz-Dialog anzeigen
|
||||||
|
showNoteDialog(nodeId, data.notes || '', data.color_code || '#FFF59D');
|
||||||
|
} else {
|
||||||
|
showUINotification('Fehler beim Laden der Notizen: ' + data.message, 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fehler beim Laden der Notizen:', error);
|
||||||
|
showUINotification('Fehler beim Laden der Notizen', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Layout speichern implementieren
|
||||||
|
document.getElementById('save-layout-btn').addEventListener('click', function() {
|
||||||
|
const mindmapId = parseInt("{{ mindmap.id }}");
|
||||||
|
const nodes = [];
|
||||||
|
|
||||||
|
// Sammle die Positionen aller Knoten
|
||||||
|
cy.nodes().forEach(node => {
|
||||||
|
nodes.push({
|
||||||
|
id: node.id(),
|
||||||
|
x: node.position().x,
|
||||||
|
y: node.position().y
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Layout speichern
|
||||||
|
fetch(`/api/mindmap/${mindmapId}/layout`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ nodes: nodes })
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showUINotification('Layout erfolgreich gespeichert', 'success');
|
||||||
|
} else {
|
||||||
|
showUINotification('Fehler beim Speichern des Layouts: ' + data.message, 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fehler beim Speichern des Layouts:', error);
|
||||||
|
showUINotification('Fehler beim Speichern des Layouts', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Öffentliche Mindmap anzeigen/verbergen
|
||||||
|
document.getElementById('toggle-public-mindmap-btn').addEventListener('click', function() {
|
||||||
|
const container = document.getElementById('public-mindmap-container');
|
||||||
|
if (container.classList.contains('hidden')) {
|
||||||
|
container.classList.remove('hidden');
|
||||||
|
this.querySelector('span').textContent = 'Öffentliche Mindmap verbergen';
|
||||||
|
loadPublicMindmap();
|
||||||
|
} else {
|
||||||
|
container.classList.add('hidden');
|
||||||
|
this.querySelector('span').textContent = 'Öffentliche Mindmap';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Funktion zum Anzeigen des Notiz-Dialogs
|
||||||
|
function showNoteDialog(nodeId, noteContent, colorCode) {
|
||||||
|
// Dialog erstellen
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
|
||||||
|
overlay.id = 'note-dialog-overlay';
|
||||||
|
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4">
|
||||||
|
<h3 class="text-xl font-bold mb-4">Notizen bearbeiten</h3>
|
||||||
|
<textarea id="note-content" class="w-full h-40 p-2 border rounded-lg mb-4 dark:bg-gray-700 dark:border-gray-600 dark:text-white">${noteContent}</textarea>
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<label class="mr-2">Farbe:</label>
|
||||||
|
<input type="color" id="note-color" value="${colorCode}" class="cursor-pointer">
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<button id="cancel-note" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-lg">Abbrechen</button>
|
||||||
|
<button id="save-note" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg">Speichern</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
// Event-Listener für Buttons
|
||||||
|
document.getElementById('cancel-note').addEventListener('click', function() {
|
||||||
|
document.getElementById('note-dialog-overlay').remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('save-note').addEventListener('click', function() {
|
||||||
|
const noteContent = document.getElementById('note-content').value;
|
||||||
|
const colorCode = document.getElementById('note-color').value;
|
||||||
|
|
||||||
|
// Notizen speichern
|
||||||
|
fetch(`/api/mindmap/node/${nodeId}/notes`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
notes: noteContent,
|
||||||
|
color_code: colorCode
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showUINotification('Notizen erfolgreich gespeichert', 'success');
|
||||||
|
document.getElementById('note-dialog-overlay').remove();
|
||||||
|
|
||||||
|
// Knoten visuell markieren, der eine Notiz hat
|
||||||
|
if (noteContent.trim() !== '') {
|
||||||
|
cy.$id(nodeId).addClass('has-note');
|
||||||
|
|
||||||
|
// Stil für Knoten mit Notizen hinzufügen, falls noch nicht vorhanden
|
||||||
|
if (!cy.style().selector('node.has-note').style('border-color')) {
|
||||||
|
cy.style()
|
||||||
|
.selector('node.has-note')
|
||||||
|
.style({
|
||||||
|
'border-color': colorCode,
|
||||||
|
'border-width': 3
|
||||||
|
})
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cy.$id(nodeId).removeClass('has-note');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showUINotification('Fehler beim Speichern der Notizen: ' + data.message, 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fehler beim Speichern der Notizen:', error);
|
||||||
|
showUINotification('Fehler beim Speichern der Notizen', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funktion zum Laden der öffentlichen Mindmap
|
||||||
|
function loadPublicMindmap() {
|
||||||
|
const container = document.getElementById('public-cy');
|
||||||
|
|
||||||
|
// Lade-Anzeige
|
||||||
|
container.innerHTML = '<div class="flex justify-center items-center h-full"><p>Lade öffentliche Mindmap...</p></div>';
|
||||||
|
|
||||||
|
// Daten abrufen
|
||||||
|
fetch('/api/public-mindmap')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
container.innerHTML = '';
|
||||||
|
|
||||||
|
// Initialisiere Cytoscape für die öffentliche Mindmap
|
||||||
|
const publicCy = cytoscape({
|
||||||
|
container: container,
|
||||||
|
elements: [],
|
||||||
|
style: [
|
||||||
|
{
|
||||||
|
selector: 'node',
|
||||||
|
style: {
|
||||||
|
'background-color': 'data(color_code)',
|
||||||
|
'label': 'data(name)',
|
||||||
|
'color': '#FFFFFF',
|
||||||
|
'text-valign': 'center',
|
||||||
|
'text-halign': 'center',
|
||||||
|
'width': '30px',
|
||||||
|
'height': '30px',
|
||||||
|
'text-wrap': 'wrap',
|
||||||
|
'text-max-width': '80px',
|
||||||
|
'font-size': '10px',
|
||||||
|
'border-width': '2px',
|
||||||
|
'border-color': '#FFFFFF',
|
||||||
|
'text-outline-color': '#555',
|
||||||
|
'text-outline-width': 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'edge',
|
||||||
|
style: {
|
||||||
|
'width': 1,
|
||||||
|
'line-color': '#9CA3AF',
|
||||||
|
'target-arrow-color': '#9CA3AF',
|
||||||
|
'target-arrow-shape': 'triangle',
|
||||||
|
'curve-style': 'bezier'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'node:selected',
|
||||||
|
style: {
|
||||||
|
'border-color': '#F59E0B',
|
||||||
|
'border-width': '3px'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
layout: {
|
||||||
|
name: 'cose',
|
||||||
|
padding: 30
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Elemente zur öffentlichen Mindmap hinzufügen
|
||||||
|
const elements = [];
|
||||||
|
|
||||||
|
// Knoten hinzufügen
|
||||||
|
data.nodes.forEach(node => {
|
||||||
|
elements.push({
|
||||||
|
group: 'nodes',
|
||||||
|
data: {
|
||||||
|
id: 'pub_' + node.id,
|
||||||
|
originalId: node.id,
|
||||||
|
name: node.name,
|
||||||
|
description: node.description,
|
||||||
|
color_code: node.color_code
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Kanten hinzufügen
|
||||||
|
data.edges.forEach(edge => {
|
||||||
|
elements.push({
|
||||||
|
group: 'edges',
|
||||||
|
data: {
|
||||||
|
source: 'pub_' + edge.source,
|
||||||
|
target: 'pub_' + edge.target
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
publicCy.add(elements);
|
||||||
|
publicCy.layout({ name: 'cose' }).run();
|
||||||
|
|
||||||
|
// Event-Listener für Knoten-Klicks in der öffentlichen Mindmap
|
||||||
|
publicCy.on('tap', 'node', function(evt) {
|
||||||
|
const node = evt.target;
|
||||||
|
const nodeData = node.data();
|
||||||
|
|
||||||
|
// Dialog zum Hinzufügen des Knotens anzeigen
|
||||||
|
showAddNodeDialog(nodeData.originalId, nodeData.name, nodeData.description);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
container.innerHTML = '<div class="flex justify-center items-center h-full"><p class="text-red-500">Fehler beim Laden der öffentlichen Mindmap</p></div>';
|
||||||
|
showUINotification('Fehler beim Laden der öffentlichen Mindmap: ' + data.message, 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fehler beim Laden der öffentlichen Mindmap:', error);
|
||||||
|
container.innerHTML = '<div class="flex justify-center items-center h-full"><p class="text-red-500">Fehler beim Laden der öffentlichen Mindmap</p></div>';
|
||||||
|
showUINotification('Fehler beim Laden der öffentlichen Mindmap', 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funktion zum Anzeigen des Dialogs zum Hinzufügen eines Knotens
|
||||||
|
function showAddNodeDialog(nodeId, nodeName, nodeDescription) {
|
||||||
|
// Dialog erstellen
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50';
|
||||||
|
overlay.id = 'add-node-dialog-overlay';
|
||||||
|
|
||||||
|
overlay.innerHTML = `
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-6 max-w-md w-full mx-4">
|
||||||
|
<h3 class="text-xl font-bold mb-4">Knoten zu Ihrer Mindmap hinzufügen</h3>
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="font-semibold">${nodeName}</p>
|
||||||
|
<p class="text-sm text-gray-600 dark:text-gray-400">${nodeDescription || 'Keine Beschreibung'}</p>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="block mb-2">Mit Knoten verbinden (optional):</label>
|
||||||
|
<select id="parent-node-select" class="w-full p-2 border rounded-lg dark:bg-gray-700 dark:border-gray-600 dark:text-white">
|
||||||
|
<option value="">Nicht verbinden</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<button id="cancel-add-node" class="px-4 py-2 bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 rounded-lg">Abbrechen</button>
|
||||||
|
<button id="confirm-add-node" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-lg">Hinzufügen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
|
||||||
|
// Fülle den Parent-Node-Selector mit vorhandenen Knoten
|
||||||
|
const parentSelect = document.getElementById('parent-node-select');
|
||||||
|
cy.nodes().forEach(node => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = node.id();
|
||||||
|
option.textContent = node.data('label');
|
||||||
|
parentSelect.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Event-Listener für Buttons
|
||||||
|
document.getElementById('cancel-add-node').addEventListener('click', function() {
|
||||||
|
document.getElementById('add-node-dialog-overlay').remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('confirm-add-node').addEventListener('click', function() {
|
||||||
|
const parentNodeId = document.getElementById('parent-node-select').value;
|
||||||
|
const mindmapId = parseInt("{{ mindmap.id }}");
|
||||||
|
|
||||||
|
// Generiere Startposition basierend auf Parent oder Zufallsposition
|
||||||
|
let position = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
if (parentNodeId) {
|
||||||
|
const parentNode = cy.$id(parentNodeId);
|
||||||
|
if (parentNode) {
|
||||||
|
// Positioniere in der Nähe des Elternknotens
|
||||||
|
position = {
|
||||||
|
x: parentNode.position('x') + Math.random() * 100 - 50,
|
||||||
|
y: parentNode.position('y') + Math.random() * 100 - 50
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Zufällige Position im sichtbaren Bereich
|
||||||
|
const extent = cy.extent();
|
||||||
|
position = {
|
||||||
|
x: extent.x1 + Math.random() * (extent.x2 - extent.x1),
|
||||||
|
y: extent.y1 + Math.random() * (extent.y2 - extent.y1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Knoten zur Mindmap hinzufügen
|
||||||
|
fetch(`/api/mindmap/${mindmapId}/add_node`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
node_id: nodeId,
|
||||||
|
parent_id: parentNodeId || null,
|
||||||
|
x: position.x,
|
||||||
|
y: position.y
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
showUINotification('Knoten erfolgreich hinzugefügt', 'success');
|
||||||
|
document.getElementById('add-node-dialog-overlay').remove();
|
||||||
|
|
||||||
|
// Aktualisiere die Mindmap (neu laden oder Knoten hinzufügen)
|
||||||
|
// Option 1: Seite neu laden
|
||||||
|
// window.location.reload();
|
||||||
|
|
||||||
|
// Option 2: Knoten dynamisch hinzufügen (eleganter)
|
||||||
|
fetch(`/api/mindmap/node/${nodeId}`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(nodeData => {
|
||||||
|
// Füge den neuen Knoten zur Anzeige hinzu
|
||||||
|
cy.add({
|
||||||
|
group: 'nodes',
|
||||||
|
data: {
|
||||||
|
id: nodeId,
|
||||||
|
label: nodeData.name,
|
||||||
|
description: nodeData.description,
|
||||||
|
color: nodeData.color_code,
|
||||||
|
fontColor: '#FFFFFF'
|
||||||
|
},
|
||||||
|
position: position
|
||||||
|
});
|
||||||
|
|
||||||
|
// Füge auch die Kante hinzu, falls ein Elternknoten ausgewählt wurde
|
||||||
|
if (parentNodeId) {
|
||||||
|
cy.add({
|
||||||
|
group: 'edges',
|
||||||
|
data: {
|
||||||
|
source: parentNodeId,
|
||||||
|
target: nodeId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wähle den neuen Knoten aus
|
||||||
|
cy.$id(nodeId).select();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fehler beim Laden der Knotendaten:', error);
|
||||||
|
// Fallback: Seite neu laden
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
showUINotification('Fehler beim Hinzufügen des Knotens: ' + (data.message || data.error), 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Fehler beim Hinzufügen des Knotens:', error);
|
||||||
|
showUINotification('Fehler beim Hinzufügen des Knotens', 'error');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Fallback, falls mindmapData null ist
|
// Fallback, falls mindmapData null ist
|
||||||
|
|||||||
Reference in New Issue
Block a user