chore: Änderungen commited
This commit is contained in:
@@ -231,16 +231,16 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-body">
|
<div class="form-body">
|
||||||
<form action="{{ url_for('edit_mindmap', mindmap_id=mindmap.id) }}" method="POST">
|
<form id="edit-mindmap-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="name" class="form-label">Name der Mindmap</label>
|
<label for="name" class="form-label">Name der Mindmap</label>
|
||||||
<input type="text" id="name" name="name" class="form-input input-animation" required
|
<input type="text" id="name" name="name" class="form-input input-animation" required
|
||||||
placeholder="z.B. Meine Philosophie-Mindmap" value="{{ mindmap.name }}">
|
placeholder="z.B. Meine Philosophie-Mindmap" value="{{ mindmap.name }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="description" class="form-label">Beschreibung</label>
|
<label for="description" class="form-label">Beschreibung</label>
|
||||||
<textarea id="description" name="description" class="form-textarea input-animation"
|
<textarea id="description" name="description" class="form-textarea input-animation"
|
||||||
placeholder="Worum geht es in dieser Mindmap?">{{ mindmap.description }}</textarea>
|
placeholder="Worum geht es in dieser Mindmap?">{{ mindmap.description }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -253,11 +253,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-between mt-6">
|
<div class="flex justify-between mt-6">
|
||||||
<a href="{{ url_for('mindmap', mindmap_id=mindmap.id) }}" class="btn-cancel">
|
<a href="{{ url_for('my_account') }}" class="btn-cancel"> {# Zurück zur Kontoübersicht geändert #}
|
||||||
<i class="fas fa-arrow-left"></i>
|
<i class="fas fa-arrow-left"></i>
|
||||||
Zurück
|
Zurück
|
||||||
</a>
|
</a>
|
||||||
<button type="submit" class="btn-submit">
|
<button type="button" id="save-mindmap-details-btn" class="btn-submit"> {# type="button" und ID hinzugefügt #}
|
||||||
<i class="fas fa-save"></i>
|
<i class="fas fa-save"></i>
|
||||||
Änderungen speichern
|
Änderungen speichern
|
||||||
</button>
|
</button>
|
||||||
@@ -322,13 +322,60 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Formular-Absenden-Animation
|
// Formular-Absenden-Logik für Metadaten
|
||||||
const form = document.querySelector('form');
|
const editMindmapForm = document.getElementById('edit-mindmap-form');
|
||||||
form.addEventListener('submit', function(e) {
|
const saveDetailsBtn = document.getElementById('save-mindmap-details-btn');
|
||||||
const submitBtn = this.querySelector('.btn-submit');
|
|
||||||
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Wird gespeichert...';
|
if (saveDetailsBtn && editMindmapForm) {
|
||||||
submitBtn.disabled = true;
|
saveDetailsBtn.addEventListener('click', async function(event) {
|
||||||
});
|
event.preventDefault();
|
||||||
|
|
||||||
|
const nameInput = document.getElementById('name');
|
||||||
|
const descriptionInput = document.getElementById('description');
|
||||||
|
const isPrivateInput = document.getElementById('is_private');
|
||||||
|
|
||||||
|
const mindmapId = "{{ mindmap.id }}"; // Sicherstellen, dass mindmap.id hier verfügbar ist
|
||||||
|
const csrfToken = "{{ csrf_token() }}";
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
name: nameInput.value,
|
||||||
|
description: descriptionInput.value,
|
||||||
|
is_private: isPrivateInput.checked
|
||||||
|
// Die 'data' (Knoten/Kanten) wird separat vom Cytoscape-Editor gehandhabt
|
||||||
|
};
|
||||||
|
|
||||||
|
saveDetailsBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Wird gespeichert...';
|
||||||
|
saveDetailsBtn.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/mindmaps/${mindmapId}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result = await response.json();
|
||||||
|
showStatus('Metadaten erfolgreich gespeichert!', false);
|
||||||
|
// Optional: Weiterleitung oder Aktualisierung der Seiteninhalte
|
||||||
|
// window.location.href = "{{ url_for('my_account') }}";
|
||||||
|
} else {
|
||||||
|
const errorData = await response.json();
|
||||||
|
console.error('Fehler beim Speichern der Metadaten:', errorData);
|
||||||
|
showStatus(`Fehler: ${errorData.error || response.statusText}`, true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Netzwerkfehler oder anderer Fehler:', error);
|
||||||
|
showStatus('Speichern fehlgeschlagen. Netzwerkproblem?', true);
|
||||||
|
} finally {
|
||||||
|
saveDetailsBtn.innerHTML = '<i class="fas fa-save"></i> Änderungen speichern';
|
||||||
|
saveDetailsBtn.disabled = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Mindmap initialisieren
|
// Mindmap initialisieren
|
||||||
const mindmap = new MindMap.Visualization('cy', {
|
const mindmap = new MindMap.Visualization('cy', {
|
||||||
@@ -337,56 +384,116 @@
|
|||||||
onNodeClick: function(nodeData) {
|
onNodeClick: function(nodeData) {
|
||||||
console.log("Knoten ausgewählt:", nodeData);
|
console.log("Knoten ausgewählt:", nodeData);
|
||||||
},
|
},
|
||||||
onChange: function(data) {
|
onChange: function(dataFromCytoscape) {
|
||||||
// Automatisches Speichern bei Änderungen
|
// Automatisches Speichern bei Änderungen der Mindmap-Struktur
|
||||||
fetch('/api/mindmap/{{ mindmap.id }}/update', {
|
// Die Metadaten (Name, Beschreibung, is_private) werden separat über das Formular oben gespeichert.
|
||||||
method: 'POST',
|
// Diese onChange Funktion kümmert sich nur um die Strukturdaten (Knoten/Kanten).
|
||||||
headers: {
|
const mindmapId = "{{ mindmap.id }}";
|
||||||
'Content-Type': 'application/json',
|
const csrfToken = "{{ csrf_token() }}";
|
||||||
'X-CSRFToken': '{{ csrf_token() }}'
|
|
||||||
},
|
// Debounce-Funktion, um API-Aufrufe zu limitieren
|
||||||
body: JSON.stringify(data)
|
let debounceTimer;
|
||||||
}).then(response => {
|
const debounceSaveStructure = (currentMindmapData) => {
|
||||||
if (!response.ok) {
|
clearTimeout(debounceTimer);
|
||||||
throw new Error('Netzwerkfehler beim Speichern');
|
debounceTimer = setTimeout(() => {
|
||||||
}
|
// Der Backend-Endpunkt PUT /api/mindmaps/<id> erwartet ein Objekt,
|
||||||
console.log('Änderungen gespeichert');
|
// das die zu aktualisierenden Felder enthält. Für die Struktur ist das 'data'.
|
||||||
}).catch(error => {
|
const payload = {
|
||||||
console.error('Fehler beim Speichern:', error);
|
data: currentMindmapData // Dies sind die von Cytoscape gelieferten Strukturdaten
|
||||||
alert('Fehler beim Speichern der Änderungen');
|
};
|
||||||
});
|
|
||||||
|
// showStatus('Speichere Struktur...', false); // Status wird jetzt über Event gehandhabt
|
||||||
|
fetch(`/api/mindmaps/${mindmapId}`, { // Endpunkt angepasst
|
||||||
|
method: 'PUT', // Methode zu PUT geändert
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload) // Sende die Mindmap-Daten als { data: ... }
|
||||||
|
}).then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
response.json().then(err => {
|
||||||
|
console.error('Fehler beim Speichern der Struktur:', err);
|
||||||
|
document.dispatchEvent(new CustomEvent('mindmapError', { detail: { message: `Struktur: ${err.message || err.error || 'Speicherfehler'}` } }));
|
||||||
|
}).catch(() => {
|
||||||
|
console.error('Fehler beim Speichern der Struktur, Status:', response.statusText);
|
||||||
|
document.dispatchEvent(new CustomEvent('mindmapError', { detail: { message: `Struktur: ${response.statusText}` } }));
|
||||||
|
});
|
||||||
|
// throw new Error('Netzwerkfehler beim Speichern der Struktur'); // Wird schon behandelt
|
||||||
|
return; // Verhindere weitere Verarbeitung bei Fehler
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
}).then(responseData => {
|
||||||
|
if (responseData) { // Nur wenn response.ok war
|
||||||
|
console.log('Mindmap-Struktur erfolgreich gespeichert:', responseData);
|
||||||
|
// Die responseData von einem PUT könnte die aktualisierte Mindmap oder nur eine Erfolgsmeldung sein.
|
||||||
|
// Annahme: { message: "Mindmap updated successfully", mindmap: { ... } } oder ähnlich
|
||||||
|
document.dispatchEvent(new CustomEvent('mindmapSaved', { detail: { message: 'Struktur aktualisiert!' }}));
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Netzwerkfehler oder anderer Fehler beim Speichern der Struktur:', error);
|
||||||
|
// Vermeide doppelte Fehlermeldung, falls schon durch !response.ok behandelt
|
||||||
|
if (!document.querySelector('.bg-red-500')) { // Prüft, ob schon eine Fehlermeldung angezeigt wird
|
||||||
|
document.dispatchEvent(new CustomEvent('mindmapError', { detail: { message: 'Struktur: Netzwerkfehler' } }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 1500); // Speichern 1.5 Sekunden nach der letzten Änderung
|
||||||
|
};
|
||||||
|
|
||||||
|
debounceSaveStructure(dataFromCytoscape); // Aufruf der Debounce-Funktion mit Cytoscape-Daten
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Formularfelder mit Mindmap verbinden
|
// Die Verknüpfung der Formularfelder (Name, Beschreibung) mit dem Cytoscape Root-Knoten wird entfernt,
|
||||||
const nameInput = document.getElementById('name');
|
// da die Metadaten nun über das separate Formular oben gespeichert werden und nicht mehr direkt
|
||||||
const descriptionInput = document.getElementById('description');
|
// die Cytoscape-Daten manipulieren sollen. Die Logik für mindmap.saveToServer() wurde entfernt,
|
||||||
|
// da das Speichern jetzt über den onChange Handler mit PUT /api/mindmaps/<id> erfolgt.
|
||||||
// Aktualisiere Mindmap wenn sich die Eingaben ändern
|
// const nameInput = document.getElementById('name'); // Bereits oben deklariert für Metadaten
|
||||||
nameInput.addEventListener('input', function() {
|
// nameInput.removeEventListener('input', ...); // Event Listener muss hier nicht entfernt werden, da er nicht neu hinzugefügt wird.
|
||||||
if (mindmap.cy) {
|
|
||||||
const rootNode = mindmap.cy.$('#root');
|
|
||||||
if (rootNode.length > 0) {
|
|
||||||
rootNode.data('name', this.value || 'Mindmap');
|
|
||||||
mindmap.saveToServer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialisiere die Mindmap mit existierenden Daten
|
// Initialisiere die Mindmap mit existierenden Daten
|
||||||
mindmap.initialize().then(() => {
|
mindmap.initialize().then(() => {
|
||||||
console.log("Mindmap-Editor initialisiert");
|
console.log("Mindmap-Editor initialisiert");
|
||||||
|
const mindmapId = "{{ mindmap.id }}";
|
||||||
|
const csrfToken = "{{ csrf_token() }}";
|
||||||
|
|
||||||
// Lade existierende Daten
|
// Lade existierende Daten für die Mindmap-Struktur
|
||||||
fetch('/api/mindmap/{{ mindmap.id }}/data')
|
fetch(`/api/mindmaps/${mindmapId}`, { // Endpunkt für GET angepasst
|
||||||
.then(response => response.json())
|
method: 'GET',
|
||||||
.then(data => {
|
headers: {
|
||||||
mindmap.loadData(data);
|
'Accept': 'application/json',
|
||||||
console.log("Mindmap-Daten geladen");
|
'X-CSRFToken': csrfToken
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
response.json().then(err => {
|
||||||
|
showStatus(`Fehler beim Laden: ${err.message || err.error || response.statusText}`, true);
|
||||||
|
}).catch(() => {
|
||||||
|
showStatus(`Fehler beim Laden: ${response.statusText}`, true);
|
||||||
|
});
|
||||||
|
throw new Error(`Netzwerkantwort war nicht ok: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
return response.json();
|
||||||
|
})
|
||||||
|
.then(mindmapDataFromServer => {
|
||||||
|
// Die API GET /api/mindmaps/<id> gibt ein Objekt zurück, das { id, name, description, is_private, data, ... } enthält.
|
||||||
|
// Wir brauchen nur den 'data'-Teil (Struktur) für Cytoscape.
|
||||||
|
// Die Metadaten (name, description, is_private) werden bereits serverseitig in die Formularfelder gerendert.
|
||||||
|
if (mindmapDataFromServer && mindmapDataFromServer.data) {
|
||||||
|
mindmap.loadData(mindmapDataFromServer.data); // Lade nur die Strukturdaten
|
||||||
|
console.log("Mindmap-Strukturdaten geladen:", mindmapDataFromServer.data);
|
||||||
|
showStatus("Mindmap geladen.", false);
|
||||||
|
} else {
|
||||||
|
console.error("Fehler: Mindmap-Daten (Struktur) nicht im erwarteten Format:", mindmapDataFromServer);
|
||||||
|
showStatus("Fehler: Mindmap-Struktur konnte nicht geladen werden (Formatfehler).", true);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error("Fehler beim Laden der Mindmap-Daten:", error);
|
console.error("Fehler beim Laden der Mindmap-Strukturdaten:", error);
|
||||||
alert("Fehler beim Laden der Mindmap");
|
if (!document.querySelector('.bg-red-500')) { // Prüft, ob schon eine Fehlermeldung angezeigt wird
|
||||||
|
showStatus("Laden der Struktur fehlgeschlagen.", true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
console.error("Fehler bei der Initialisierung des Editors:", error);
|
console.error("Fehler bei der Initialisierung des Editors:", error);
|
||||||
@@ -411,8 +518,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Event-Listener für Speicherstatus
|
// Event-Listener für Speicherstatus
|
||||||
document.addEventListener('mindmapSaved', () => {
|
document.addEventListener('mindmapSaved', (event) => {
|
||||||
showStatus('Änderungen gespeichert');
|
const message = event.detail && event.detail.message ? event.detail.message : 'Erfolgreich gespeichert!';
|
||||||
|
showStatus(message, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('mindmapError', (event) => {
|
document.addEventListener('mindmapError', (event) => {
|
||||||
|
|||||||
@@ -353,22 +353,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const csrfToken = "{{ csrf_token() }}"; // CSRF Token holen
|
||||||
const response = await fetch('/api/mindmaps', {
|
const response = await fetch('/api/mindmaps', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': csrfToken // CSRF Token im Header senden
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ name, description }),
|
body: JSON.stringify({ name, description, is_private: false }), // is_private standardmäßig auf false setzen
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
throw new Error(errorData.error || `HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
const newMindmap = await response.json();
|
const newMindmap = await response.json();
|
||||||
showNotification(`Mindmap "${newMindmap.name}" erfolgreich erstellt.`, 'success');
|
showNotification(`Mindmap "${newMindmap.name}" erfolgreich erstellt. Weiterleitung...`, 'success');
|
||||||
createMindmapModal.classList.add('hidden');
|
createMindmapModal.classList.add('hidden');
|
||||||
createMindmapForm.reset();
|
createMindmapForm.reset();
|
||||||
fetchUserMindmaps(); // Liste aktualisieren
|
// fetchUserMindmaps(); // Liste wird auf der neuen Seite ohnehin neu geladen oder ist nicht direkt sichtbar.
|
||||||
|
// Weiterleitung zur Bearbeitungsseite der neuen Mindmap
|
||||||
|
window.location.href = `/edit_mindmap/${newMindmap.id}`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Fehler beim Erstellen der Mindmap:', error);
|
console.error('Fehler beim Erstellen der Mindmap:', error);
|
||||||
showNotification(`Fehler beim Erstellen: ${error.message}`, 'error');
|
showNotification(`Fehler beim Erstellen: ${error.message}`, 'error');
|
||||||
|
|||||||
@@ -324,7 +324,7 @@
|
|||||||
<!-- Mindmap Container mit Positionsindikator -->
|
<!-- Mindmap Container mit Positionsindikator -->
|
||||||
<div class="relative rounded-xl overflow-hidden border transition-all duration-300"
|
<div class="relative rounded-xl overflow-hidden border transition-all duration-300"
|
||||||
x-bind:class="darkMode ? 'border-gray-700/50' : 'border-gray-300/50'">
|
x-bind:class="darkMode ? 'border-gray-700/50' : 'border-gray-300/50'">
|
||||||
<div id="cy"></div>
|
<div id="cy" data-mindmap-id="{{ mindmap.id }}"></div>
|
||||||
|
|
||||||
<!-- Informationsanzeige für ausgewählten Knoten -->
|
<!-- Informationsanzeige für ausgewählten Knoten -->
|
||||||
<div id="node-info-panel" class="node-info-panel p-4">
|
<div id="node-info-panel" class="node-info-panel p-4">
|
||||||
@@ -353,39 +353,198 @@
|
|||||||
<script src="{{ url_for('static', filename='js/cytoscape.min.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/cytoscape.min.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/mindmap-init.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/mindmap-init.js') }}"></script>
|
||||||
<script nonce="{{ csp_nonce }}">
|
<script nonce="{{ csp_nonce }}">
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', async function() {
|
||||||
// Benutzer-Mindmap-ID für die API-Anfragen
|
const cyContainer = document.getElementById('cy');
|
||||||
const mindmapId = {{ mindmap.id }};
|
if (!cyContainer) {
|
||||||
|
console.error("Mindmap container #cy not found!");
|
||||||
// Erstellt eine neue MindMap-Instanz für die Benutzer-Mindmap
|
return;
|
||||||
window.userMindmap = new MindMap('#cy', {
|
}
|
||||||
editable: true,
|
const mindmapId = cyContainer.dataset.mindmapId;
|
||||||
isUserLoggedIn: true,
|
const nodeInfoPanel = document.getElementById('node-info-panel');
|
||||||
isPublicMap: false,
|
const nodeDescription = document.getElementById('node-description');
|
||||||
userMindmapId: mindmapId,
|
const connectedNodesContainer = document.getElementById('connected-nodes');
|
||||||
fitViewOnInit: true,
|
const mindmapNameH1 = document.querySelector('h1.gradient-text');
|
||||||
callbacks: {
|
const mindmapDescriptionP = document.querySelector('p.opacity-80.mt-1');
|
||||||
onLoad: function() {
|
|
||||||
console.log('Benutzerdefinierte Mindmap wurde geladen');
|
// Funktion zum Anzeigen von Benachrichtigungen (vereinfacht)
|
||||||
|
function showUINotification(message, type = 'success') {
|
||||||
|
const notificationArea = document.getElementById('notification-area-usr') || createUINotificationArea();
|
||||||
|
const notificationId = `notif-usr-${Date.now()}`;
|
||||||
|
const bgColor = type === 'success' ? 'bg-green-500' : (type === 'error' ? 'bg-red-500' : 'bg-blue-500');
|
||||||
|
|
||||||
|
const notificationElement = `
|
||||||
|
<div id="${notificationId}" class="p-3 mb-3 text-sm text-white rounded-lg ${bgColor} animate-fadeIn" role="alert">
|
||||||
|
<span>${message}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
notificationArea.insertAdjacentHTML('beforeend', notificationElement);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const el = document.getElementById(notificationId);
|
||||||
|
if (el) {
|
||||||
|
el.classList.add('animate-fadeOut');
|
||||||
|
setTimeout(() => el.remove(), 500);
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUINotificationArea() {
|
||||||
|
const area = document.createElement('div');
|
||||||
|
area.id = 'notification-area-usr';
|
||||||
|
area.className = 'fixed top-20 right-5 z-[1001] w-auto max-w-xs'; // höhere z-index
|
||||||
|
document.body.appendChild(area);
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
.animate-fadeIn { animation: fadeIn 0.3s ease-out; }
|
||||||
|
.animate-fadeOut { animation: fadeOut 0.3s ease-in forwards; }
|
||||||
|
@keyframes fadeIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } }
|
||||||
|
@keyframes fadeOut { from { opacity: 1; transform: translateX(0); } to { opacity: 0; transform: translateX(20px); } }
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchMindmapData(id) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/mindmaps/${id}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
return await response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der Mindmap-Daten:', error);
|
||||||
|
showUINotification('Fehler beim Laden der Mindmap-Daten.', 'error');
|
||||||
|
cyContainer.innerHTML = '<p class="text-center text-red-500 p-10">Konnte Mindmap nicht laden.</p>';
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
// Event-Listener für Notiz-Button
|
const mindmapData = await fetchMindmapData(mindmapId);
|
||||||
document.getElementById('add-note-btn').addEventListener('click', function() {
|
|
||||||
// Erstellt eine neue Notiz in der Mitte des Viewports
|
if (mindmapData) {
|
||||||
const position = window.userMindmap.cy.pan();
|
if(mindmapNameH1) mindmapNameH1.textContent = mindmapData.name;
|
||||||
|
if(mindmapDescriptionP) mindmapDescriptionP.textContent = mindmapData.description || "Keine Beschreibung vorhanden.";
|
||||||
|
|
||||||
window.userMindmap.showAddNoteDialog({
|
// Cytoscape initialisieren
|
||||||
x: position.x,
|
const cy = cytoscape({
|
||||||
y: position.y
|
container: cyContainer,
|
||||||
|
elements: mindmapData.elements || [], // Verwende 'elements' aus der API-Antwort
|
||||||
|
style: [
|
||||||
|
{
|
||||||
|
selector: 'node',
|
||||||
|
style: {
|
||||||
|
'background-color': 'data(color)',
|
||||||
|
'label': 'data(label)',
|
||||||
|
'color': 'data(fontColor)',
|
||||||
|
'text-valign': 'center',
|
||||||
|
'text-halign': 'center',
|
||||||
|
'font-size': 'data(fontSize)',
|
||||||
|
'width': ele => ele.data('isCenter') ? 60 : (ele.data('size') || 40),
|
||||||
|
'height': ele => ele.data('isCenter') ? 60 : (ele.data('size') || 40),
|
||||||
|
'border-width': 2,
|
||||||
|
'border-color': '#fff',
|
||||||
|
'shape': 'ellipse',
|
||||||
|
'text-outline-color': '#555',
|
||||||
|
'text-outline-width': 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'edge',
|
||||||
|
style: {
|
||||||
|
'width': ele => ele.data('strength') ? ele.data('strength') * 1.5 : 2,
|
||||||
|
'line-color': ele => ele.data('color') || '#9dbaea',
|
||||||
|
'target-arrow-color': ele => ele.data('color') || '#9dbaea',
|
||||||
|
'target-arrow-shape': 'triangle',
|
||||||
|
'curve-style': 'bezier'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
selector: 'node:selected',
|
||||||
|
style: {
|
||||||
|
'border-color': '#f59e42',
|
||||||
|
'border-width': 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
layout: {
|
||||||
|
name: 'cose',
|
||||||
|
idealEdgeLength: 100,
|
||||||
|
nodeOverlap: 20,
|
||||||
|
refresh: 20,
|
||||||
|
fit: true,
|
||||||
|
padding: 30,
|
||||||
|
randomize: false,
|
||||||
|
componentSpacing: 100,
|
||||||
|
nodeRepulsion: 400000,
|
||||||
|
edgeElasticity: 100,
|
||||||
|
nestingFactor: 5,
|
||||||
|
gravity: 80,
|
||||||
|
numIter: 1000,
|
||||||
|
initialTemp: 200,
|
||||||
|
coolingFactor: 0.95,
|
||||||
|
minTemp: 1.0
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
window.cyInstance = cy; // Für globalen Zugriff falls nötig
|
||||||
|
|
||||||
// Event-Listener für Layout-Speichern-Button
|
cy.on('tap', 'node', function(evt){
|
||||||
document.getElementById('save-layout-btn').addEventListener('click', function() {
|
const node = evt.target;
|
||||||
window.userMindmap.saveLayout();
|
if (nodeDescription) nodeDescription.textContent = node.data('description') || 'Keine Beschreibung für diesen Knoten.';
|
||||||
});
|
|
||||||
});
|
if (connectedNodesContainer) {
|
||||||
|
connectedNodesContainer.innerHTML = '';
|
||||||
|
const connected = node.connectedEdges().otherNodes();
|
||||||
|
if (connected.length > 0) {
|
||||||
|
connected.forEach(cn => {
|
||||||
|
const link = document.createElement('span');
|
||||||
|
link.className = 'node-link';
|
||||||
|
link.textContent = cn.data('label');
|
||||||
|
link.style.backgroundColor = cn.data('color') || '#60a5fa';
|
||||||
|
link.addEventListener('click', () => {
|
||||||
|
cy.center(cn);
|
||||||
|
cn.select();
|
||||||
|
// Info Panel für den geklickten verbundenen Knoten aktualisieren
|
||||||
|
if (nodeDescription) nodeDescription.textContent = cn.data('description') || 'Keine Beschreibung für diesen Knoten.';
|
||||||
|
// Rekursiv verbundene Knoten des neu ausgewählten Knotens anzeigen (optional)
|
||||||
|
});
|
||||||
|
connectedNodesContainer.appendChild(link);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
connectedNodesContainer.innerHTML = '<p class="opacity-70 text-sm">Keine direkten Verbindungen.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nodeInfoPanel) nodeInfoPanel.classList.add('visible');
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.on('tap', function(evt){
|
||||||
|
if(evt.target === cy){ // Klick auf Hintergrund
|
||||||
|
if (nodeInfoPanel) nodeInfoPanel.classList.remove('visible');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toolbar-Buttons
|
||||||
|
document.getElementById('fit-btn')?.addEventListener('click', () => cy.fit(null, 50));
|
||||||
|
document.getElementById('reset-btn')?.addEventListener('click', () => cy.layout({name: 'cose', animate:true}).run());
|
||||||
|
|
||||||
|
let labelsVisible = true;
|
||||||
|
document.getElementById('toggle-labels-btn')?.addEventListener('click', () => {
|
||||||
|
labelsVisible = !labelsVisible;
|
||||||
|
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)
|
||||||
|
document.getElementById('add-note-btn').addEventListener('click', function() {
|
||||||
|
showUINotification('Notizfunktion wird auf der Bearbeitungsseite implementiert.', 'info');
|
||||||
|
});
|
||||||
|
document.getElementById('save-layout-btn').addEventListener('click', function() {
|
||||||
|
showUINotification('Layout-Speicherung wird auf der Bearbeitungsseite implementiert.', 'info');
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Fallback, falls mindmapData null ist
|
||||||
|
if(mindmapNameH1) mindmapNameH1.textContent = "Mindmap nicht gefunden";
|
||||||
|
cyContainer.innerHTML = '<p class="text-center text-red-500 p-10">Die angeforderte Mindmap konnte nicht geladen werden.</p>';
|
||||||
|
}
|
||||||
|
}); // End of DOMContentLoaded
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user