525 lines
19 KiB
HTML
525 lines
19 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Mindmap bearbeiten{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
/* Spezifische Stile für die Mindmap-Bearbeitungsseite */
|
|
.form-container {
|
|
background-color: var(--bg-secondary);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
body.dark .form-container {
|
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.2);
|
|
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
body:not(.dark) .form-container {
|
|
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
|
|
border: 1px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.form-header {
|
|
padding: 1.5rem;
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
body:not(.dark) .form-header {
|
|
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.form-body {
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.form-label {
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.form-input,
|
|
.form-textarea {
|
|
width: 100%;
|
|
padding: 0.75rem 1rem;
|
|
border-radius: 0.5rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
body.dark .form-input,
|
|
body.dark .form-textarea {
|
|
background-color: rgba(15, 23, 42, 0.6);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
color: #f1f5f9;
|
|
}
|
|
|
|
body:not(.dark) .form-input,
|
|
body:not(.dark) .form-textarea {
|
|
background-color: white;
|
|
border: 1px solid #e2e8f0;
|
|
color: #334155;
|
|
}
|
|
|
|
body.dark .form-input:focus,
|
|
body.dark .form-textarea:focus {
|
|
border-color: #7c3aed;
|
|
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.2);
|
|
outline: none;
|
|
}
|
|
|
|
body:not(.dark) .form-input:focus,
|
|
body:not(.dark) .form-textarea:focus {
|
|
border-color: #7c3aed;
|
|
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.2);
|
|
outline: none;
|
|
}
|
|
|
|
.form-textarea {
|
|
min-height: 120px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.form-switch {
|
|
display: flex;
|
|
align-items: center;
|
|
}
|
|
|
|
.form-switch input[type="checkbox"] {
|
|
height: 0;
|
|
width: 0;
|
|
visibility: hidden;
|
|
position: absolute;
|
|
}
|
|
|
|
.form-switch label {
|
|
cursor: pointer;
|
|
width: 50px;
|
|
height: 25px;
|
|
background: rgba(100, 116, 139, 0.3);
|
|
display: block;
|
|
border-radius: 25px;
|
|
position: relative;
|
|
margin-right: 10px;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.form-switch label:after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 3px;
|
|
left: 3px;
|
|
width: 19px;
|
|
height: 19px;
|
|
background: #fff;
|
|
border-radius: 19px;
|
|
transition: 0.3s;
|
|
}
|
|
|
|
.form-switch input:checked + label {
|
|
background: #7c3aed;
|
|
}
|
|
|
|
.form-switch input:checked + label:after {
|
|
left: calc(100% - 3px);
|
|
transform: translateX(-100%);
|
|
}
|
|
|
|
.btn-submit {
|
|
background-color: #7c3aed;
|
|
color: white;
|
|
padding: 0.75rem 1.5rem;
|
|
border-radius: 0.5rem;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
border: none;
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.btn-submit:hover {
|
|
background-color: #6d28d9;
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(109, 40, 217, 0.2);
|
|
}
|
|
|
|
.btn-cancel {
|
|
background-color: transparent;
|
|
padding: 0.75rem 1.5rem;
|
|
border-radius: 0.5rem;
|
|
font-weight: 500;
|
|
transition: all 0.3s ease;
|
|
border: 1px solid;
|
|
cursor: pointer;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
body.dark .btn-cancel {
|
|
color: #e2e8f0;
|
|
border-color: rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
body:not(.dark) .btn-cancel {
|
|
color: #475569;
|
|
border-color: #e2e8f0;
|
|
}
|
|
|
|
.btn-cancel:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
body.dark .btn-cancel:hover {
|
|
background-color: rgba(255, 255, 255, 0.05);
|
|
}
|
|
|
|
body:not(.dark) .btn-cancel:hover {
|
|
background-color: rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
/* Animation für den Seiteneintritt */
|
|
@keyframes slideInUp {
|
|
from {
|
|
transform: translateY(20px);
|
|
opacity: 0;
|
|
}
|
|
to {
|
|
transform: translateY(0);
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
.form-container {
|
|
animation: slideInUp 0.5s ease forwards;
|
|
}
|
|
|
|
/* Animation für Hover-Effekte */
|
|
.input-animation {
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
}
|
|
|
|
.input-animation:focus {
|
|
transform: scale(1.01);
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container mx-auto px-4 py-8 animate-fadeIn">
|
|
<div class="max-w-3xl mx-auto">
|
|
<!-- Titel mit Animation -->
|
|
<div class="text-center mb-8 animate-pulse">
|
|
<h1 class="text-3xl font-bold mb-2 mystical-glow gradient-text">
|
|
Mindmap bearbeiten
|
|
</h1>
|
|
<p class="opacity-80">Aktualisiere die Details deiner Mindmap</p>
|
|
</div>
|
|
|
|
<div class="form-container">
|
|
<div class="form-header">
|
|
<h2 class="text-xl font-semibold">Mindmap-Details</h2>
|
|
</div>
|
|
|
|
<div class="form-body">
|
|
<form id="edit-mindmap-form">
|
|
<div class="form-group">
|
|
<label for="name" class="form-label">Name der Mindmap</label>
|
|
<input type="text" id="name" name="name" class="form-input input-animation" required
|
|
placeholder="z.B. Meine Philosophie-Mindmap" value="{{ mindmap.name }}">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="description" class="form-label">Beschreibung</label>
|
|
<textarea id="description" name="description" class="form-textarea input-animation"
|
|
placeholder="Worum geht es in dieser Mindmap?">{{ mindmap.description }}</textarea>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="form-switch">
|
|
<input type="checkbox" id="is_private" name="is_private" {% if mindmap.is_private %}checked{% endif %}>
|
|
<label for="is_private"></label>
|
|
<span>Private Mindmap (nur für dich sichtbar)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-between mt-6">
|
|
<a href="{{ url_for('my_account') }}" class="btn-cancel"> {# Zurück zur Kontoübersicht geändert #}
|
|
<i class="fas fa-arrow-left"></i>
|
|
Zurück
|
|
</a>
|
|
<button type="button" id="save-mindmap-details-btn" class="btn-submit"> {# type="button" und ID hinzugefügt #}
|
|
<i class="fas fa-save"></i>
|
|
Änderungen speichern
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Mindmap-Editor -->
|
|
<div class="mt-8">
|
|
<h3 class="text-xl font-semibold mb-4">Mindmap bearbeiten</h3>
|
|
<div class="mindmap-container">
|
|
<div id="cy" class="w-full h-[600px] rounded-xl border"
|
|
x-bind:class="darkMode ? 'border-gray-700' : 'border-gray-200'">
|
|
</div>
|
|
</div>
|
|
<!-- Bearbeitungshinweise -->
|
|
<div class="mt-4 text-sm opacity-80">
|
|
<p><i class="fas fa-info-circle mr-2"></i>Klicke auf Knoten zum Bearbeiten, ziehe sie zum Neuanordnen oder nutze die Toolbar für weitere Funktionen.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tipps-Sektion -->
|
|
<div class="mt-8 p-5 rounded-lg border animate-fadeIn"
|
|
x-bind:class="darkMode ? 'bg-slate-800/40 border-slate-700/50' : 'bg-white border-slate-200'">
|
|
<h3 class="text-xl font-semibold mb-3"
|
|
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
|
<i class="fa-solid fa-lightbulb text-yellow-400 mr-2"></i>Tipps zum Bearbeiten einer Mindmap
|
|
</h3>
|
|
<div x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
|
<ul class="list-disc pl-5 space-y-2">
|
|
<li>Überprüfe, ob der Name noch zum aktuellen Inhalt passt</li>
|
|
<li>Aktualisiere die Beschreibung, um neue Aspekte zu berücksichtigen</li>
|
|
<li>Entscheide, ob die Sichtbarkeitseinstellungen noch passend sind</li>
|
|
<li>Nutze aussagekräftige Namen für bessere Auffindbarkeit</li>
|
|
<li>Behalte die Konsistenz mit verknüpften Konzepten im Auge</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape-cose-bilkent/4.1.0/cytoscape-cose-bilkent.min.js"></script>
|
|
<script nonce="{{ csp_nonce }}">
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Einfache Animationen für die Eingabefelder
|
|
const inputs = document.querySelectorAll('.input-animation');
|
|
|
|
inputs.forEach(input => {
|
|
// Subtile Skalierung bei Fokus
|
|
input.addEventListener('focus', function() {
|
|
this.style.transform = 'scale(1.01)';
|
|
this.style.boxShadow = '0 4px 12px rgba(124, 58, 237, 0.15)';
|
|
});
|
|
|
|
input.addEventListener('blur', function() {
|
|
this.style.transform = 'scale(1)';
|
|
this.style.boxShadow = 'none';
|
|
});
|
|
});
|
|
|
|
// Formular-Absenden-Logik für Metadaten
|
|
const editMindmapForm = document.getElementById('edit-mindmap-form');
|
|
const saveDetailsBtn = document.getElementById('save-mindmap-details-btn');
|
|
|
|
if (saveDetailsBtn && editMindmapForm) {
|
|
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 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'
|
|
},
|
|
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
|
|
const mindmap = new MindMap.Visualization('cy', {
|
|
enableEditing: true,
|
|
apiEndpoint: '/api/mindmap/{{ mindmap.id }}',
|
|
onNodeClick: function(nodeData) {
|
|
console.log("Knoten ausgewählt:", nodeData);
|
|
},
|
|
onChange: function(dataFromCytoscape) {
|
|
// Automatisches Speichern bei Änderungen der Mindmap-Struktur
|
|
// Die Metadaten (Name, Beschreibung, is_private) werden separat über das Formular oben gespeichert.
|
|
// Diese onChange Funktion kümmert sich nur um die Strukturdaten (Knoten/Kanten).
|
|
const mindmapId = "{{ mindmap.id }}";
|
|
|
|
// Debounce-Funktion, um API-Aufrufe zu limitieren
|
|
let debounceTimer;
|
|
const debounceSaveStructure = (currentMindmapData) => {
|
|
clearTimeout(debounceTimer);
|
|
debounceTimer = setTimeout(() => {
|
|
// Der Backend-Endpunkt PUT /api/mindmaps/<id> erwartet ein Objekt,
|
|
// das die zu aktualisierenden Felder enthält. Für die Struktur ist das 'data'.
|
|
const payload = {
|
|
data: currentMindmapData // Dies sind die von Cytoscape gelieferten Strukturdaten
|
|
};
|
|
|
|
// 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'
|
|
},
|
|
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
|
|
}
|
|
});
|
|
|
|
// Die Verknüpfung der Formularfelder (Name, Beschreibung) mit dem Cytoscape Root-Knoten wird entfernt,
|
|
// da die Metadaten nun über das separate Formular oben gespeichert werden und nicht mehr direkt
|
|
// 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.
|
|
// const nameInput = document.getElementById('name'); // Bereits oben deklariert für Metadaten
|
|
// nameInput.removeEventListener('input', ...); // Event Listener muss hier nicht entfernt werden, da er nicht neu hinzugefügt wird.
|
|
|
|
// Initialisiere die Mindmap mit existierenden Daten
|
|
mindmap.initialize().then(() => {
|
|
console.log("Mindmap-Editor initialisiert");
|
|
const mindmapId = "{{ mindmap.id }}";
|
|
|
|
// Lade existierende Daten für die Mindmap-Struktur
|
|
fetch(`/api/mindmaps/${mindmapId}`, { // Endpunkt für GET angepasst
|
|
method: 'GET',
|
|
headers: {
|
|
'Accept': 'application/json'
|
|
}
|
|
})
|
|
.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 => {
|
|
console.error("Fehler beim Laden der Mindmap-Strukturdaten:", error);
|
|
if (!document.querySelector('.bg-red-500')) { // Prüft, ob schon eine Fehlermeldung angezeigt wird
|
|
showStatus("Laden der Struktur fehlgeschlagen.", true);
|
|
}
|
|
});
|
|
}).catch(error => {
|
|
console.error("Fehler bei der Initialisierung des Editors:", error);
|
|
});
|
|
|
|
// Autosave-Status Anzeige
|
|
const statusIndicator = document.createElement('div');
|
|
statusIndicator.className = 'fixed bottom-4 right-4 px-4 py-2 rounded-full text-sm transition-all duration-300';
|
|
document.body.appendChild(statusIndicator);
|
|
|
|
// Zeige Speicherstatus
|
|
function showStatus(message, isError = false) {
|
|
statusIndicator.textContent = message;
|
|
statusIndicator.className = `fixed bottom-4 right-4 px-4 py-2 rounded-full text-sm transition-all duration-300 ${
|
|
isError
|
|
? 'bg-red-500 text-white'
|
|
: 'bg-green-500 text-white'
|
|
}`;
|
|
setTimeout(() => {
|
|
statusIndicator.className = 'fixed bottom-4 right-4 px-4 py-2 rounded-full text-sm transition-all duration-300 opacity-0';
|
|
}, 2000);
|
|
}
|
|
|
|
// Event-Listener für Speicherstatus
|
|
document.addEventListener('mindmapSaved', (event) => {
|
|
const message = event.detail && event.detail.message ? event.detail.message : 'Erfolgreich gespeichert!';
|
|
showStatus(message, false);
|
|
});
|
|
|
|
document.addEventListener('mindmapError', (event) => {
|
|
showStatus(event.detail.message, true);
|
|
});
|
|
});
|
|
</script>
|
|
{% endblock %} |