From d1352286b717d21459ea2d03f1065b522c99c375 Mon Sep 17 00:00:00 2001 From: Till Tomczak Date: Sat, 10 May 2025 23:10:31 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20=C3=84nderungen=20commited?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 132 +++++++++++++++++ templates/user_mindmap.html | 279 ++++++++++++++++++++++++++++++++++++ 2 files changed, 411 insertions(+) diff --git a/app.py b/app.py index c761c6c..e73354b 100644 --- a/app.py +++ b/app.py @@ -2403,6 +2403,138 @@ def search_mindmap_nodes(): 'results': [] }), 500 +# Export/Import-Funktionen für Mindmaps +@app.route('/api/mindmap//export', methods=['GET']) +@login_required +def export_mindmap(mindmap_id): + """ + Exportiert eine Mindmap im angegebenen Format. + + Query-Parameter: + - format: Format der Exportdatei (json, xml, csv) + """ + try: + # Sicherheitscheck: Nur eigene Mindmaps oder Mindmaps, auf die der Benutzer Zugriff hat + mindmap = UserMindmap.query.get_or_404(mindmap_id) + + # Prüfen, ob der Benutzer Zugriff auf diese Mindmap hat + can_access = mindmap.user_id == current_user.id + + if not can_access and mindmap.is_private: + return jsonify({ + 'success': False, + 'message': 'Keine Berechtigung für den Zugriff auf diese Mindmap' + }), 403 + + # Format aus Query-Parameter holen + export_format = request.args.get('format', 'json') + + # Alle Knoten und ihre Positionen in dieser Mindmap holen + nodes_data = db.session.query( + MindMapNode, UserMindmapNode + ).join( + UserMindmapNode, UserMindmapNode.node_id == MindMapNode.id + ).filter( + UserMindmapNode.user_mindmap_id == mindmap_id + ).all() + + # Beziehungen zwischen Knoten holen + relationships = [] + for node1, user_node1 in nodes_data: + for node2, user_node2 in nodes_data: + if node1.id != node2.id and node2 in node1.children: + relationships.append({ + 'source': node1.id, + 'target': node2.id + }) + + # Exportdaten vorbereiten + export_data = { + 'mindmap': { + 'id': mindmap.id, + 'name': mindmap.name, + 'description': mindmap.description, + 'created_at': mindmap.created_at.isoformat(), + 'last_modified': mindmap.last_modified.isoformat() + }, + 'nodes': [{ + 'id': node.id, + 'name': node.name, + 'description': node.description or '', + 'color_code': node.color_code or '#9F7AEA', + 'x_position': user_node.x_position, + 'y_position': user_node.y_position, + 'scale': user_node.scale or 1.0 + } for node, user_node in nodes_data], + 'relationships': relationships + } + + # Exportieren im angeforderten Format + if export_format == 'json': + response = app.response_class( + response=json.dumps(export_data, indent=2), + status=200, + mimetype='application/json' + ) + response.headers["Content-Disposition"] = f"attachment; filename=mindmap_{mindmap_id}.json" + return response + + elif export_format == 'xml': + import dicttoxml + xml_data = dicttoxml.dicttoxml(export_data) + response = app.response_class( + response=xml_data, + status=200, + mimetype='application/xml' + ) + response.headers["Content-Disposition"] = f"attachment; filename=mindmap_{mindmap_id}.xml" + return response + + elif export_format == 'csv': + import io + import csv + + # CSV kann nicht die gesamte Struktur darstellen, daher nur die Knotenliste + output = io.StringIO() + writer = csv.writer(output) + + # Schreibe Header + writer.writerow(['id', 'name', 'description', 'color_code', 'x_position', 'y_position', 'scale']) + + # Schreibe Knotendaten + for node, user_node in nodes_data: + writer.writerow([ + node.id, + node.name, + node.description or '', + node.color_code or '#9F7AEA', + user_node.x_position, + user_node.y_position, + user_node.scale or 1.0 + ]) + + output.seek(0) + response = app.response_class( + response=output.getvalue(), + status=200, + mimetype='text/csv' + ) + response.headers["Content-Disposition"] = f"attachment; filename=mindmap_{mindmap_id}_nodes.csv" + return response + + else: + return jsonify({ + 'success': False, + 'message': f'Nicht unterstütztes Format: {export_format}' + }), 400 + + except Exception as e: + print(f"Fehler beim Exportieren der Mindmap: {str(e)}") + return jsonify({ + 'success': False, + 'message': f'Fehler beim Exportieren: {str(e)}' + }), 500 + # Automatische Datenbankinitialisierung - Aktualisiert für Flask 2.2+ Kompatibilität def initialize_app(): """Initialisierung der Anwendung""" diff --git a/templates/user_mindmap.html b/templates/user_mindmap.html index 06c8274..3690af1 100644 --- a/templates/user_mindmap.html +++ b/templates/user_mindmap.html @@ -435,6 +435,19 @@
+ +
+
+

Suchergebnisse

+ +
+
+ +
+
+

Knotendetails

@@ -1051,6 +1064,272 @@ }); } + // Suchfunktionalität implementieren + const searchInput = document.getElementById('mindmap-search'); + const searchBtn = document.getElementById('search-btn'); + const searchResultsContainer = document.getElementById('search-results-container'); + const searchResultsList = document.getElementById('search-results-list'); + const closeSearchBtn = document.getElementById('close-search'); + + // Funktion zum Schließen der Suchergebnisse + function closeSearchResults() { + searchResultsContainer.classList.remove('visible'); + searchInput.value = ''; + } + + // Event-Listener für die Suche + searchBtn.addEventListener('click', performSearch); + searchInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { + performSearch(); + } + }); + + closeSearchBtn.addEventListener('click', closeSearchResults); + + // Funktion für die Suche + function performSearch() { + const query = searchInput.value.trim(); + if (!query) { + showUINotification('Bitte geben Sie einen Suchbegriff ein.', 'info'); + return; + } + + // Lade-Animation im Suchergebnisbereich anzeigen + searchResultsList.innerHTML = '
Suche läuft...
'; + searchResultsContainer.classList.add('visible'); + + // API-Anfrage für die Suche + fetch(`/api/search/mindmap?q=${encodeURIComponent(query)}`) + .then(response => response.json()) + .then(data => { + if (data.success) { + displaySearchResults(data.results, query); + } else { + searchResultsList.innerHTML = `
Fehler: ${data.message}
`; + } + }) + .catch(error => { + console.error('Fehler bei der Suche:', error); + searchResultsList.innerHTML = '
Ein Fehler ist aufgetreten.
'; + }); + } + + // Funktion zum Anzeigen der Suchergebnisse + function displaySearchResults(results, query) { + searchResultsList.innerHTML = ''; + + if (results.length === 0) { + searchResultsList.innerHTML = '
Keine Ergebnisse gefunden.
'; + return; + } + + // Hervorheben-Funktion für den Suchbegriff + function highlightText(text, query) { + if (!text) return ''; + + const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi'); + return text.replace(regex, '$1'); + } + + // Ergebnisse anzeigen + results.forEach(result => { + const item = document.createElement('div'); + item.className = 'search-result-item'; + + // Inhalte mit hervorgehobenem Suchbegriff + const highlightedName = highlightText(result.name, query); + const highlightedDesc = highlightText(result.description, query); + + item.innerHTML = ` +
+ + ${highlightedName} +
+
${highlightedDesc}
+
+ ${result.mindmap_name} +
+ `; + + // Event-Listener zum Springen zum Knoten + item.addEventListener('click', () => { + if (result.source === 'user_mindmap') { + // Knoten ist bereits in der aktuellen Mindmap + const node = cy.$id(result.id); + if (node.length > 0) { + // Knoten auswählen und zentrieren + node.select(); + cy.animate({ + center: { eles: node }, + zoom: 1.5, + duration: 500 + }); + + // Aktualisiere das Info-Panel + nodeDescription.textContent = result.description || 'Keine Beschreibung für diesen Knoten.'; + nodeInfoPanel.classList.add('visible'); + + // Suchergebnisse schließen + closeSearchResults(); + } else { + // Knoten ist in einer anderen Mindmap des Benutzers + if (result.mindmap_id && result.mindmap_id !== parseInt("{{ mindmap.id }}")) { + if (confirm(`Dieser Knoten befindet sich in der Mindmap "${result.mindmap_name}". Möchten Sie zu dieser Mindmap wechseln?`)) { + window.location.href = `/my-mindmap/${result.mindmap_id}`; + } + } else { + showUINotification('Knoten konnte nicht gefunden werden.', 'error'); + } + } + } else if (result.source === 'public') { + // Knoten ist in der öffentlichen Mindmap, frage ob er hinzugefügt werden soll + if (confirm(`Möchten Sie den Knoten "${result.name}" aus der öffentlichen Mindmap zu Ihrer Mindmap hinzufügen?`)) { + // Dialog zum Hinzufügen des Knotens anzeigen + showAddNodeDialog(result.id, result.name, result.description); + closeSearchResults(); + } + } + }); + + searchResultsList.appendChild(item); + }); + } + + // Export-Funktionalität implementieren + document.getElementById('export-btn').addEventListener('click', function() { + const mindmapId = parseInt("{{ mindmap.id }}"); + + // Dialog für Export-Format anzeigen + const overlay = document.createElement('div'); + overlay.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; + overlay.id = 'export-dialog-overlay'; + + overlay.innerHTML = ` +
+

Mindmap exportieren

+
+ + +
+
+ + +
+
+ `; + + document.body.appendChild(overlay); + + // Event-Listener für Export-Buttons + document.getElementById('cancel-export').addEventListener('click', function() { + document.getElementById('export-dialog-overlay').remove(); + }); + + document.getElementById('confirm-export').addEventListener('click', function() { + const format = document.getElementById('export-format').value; + + // API-Anfrage für den Export + fetch(`/api/mindmap/${mindmapId}/export?format=${format}`) + .then(response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return response.blob(); + }) + .then(blob => { + // Download der Export-Datei + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = `mindmap_${mindmapId}.${format}`; + document.body.appendChild(a); + a.click(); + window.URL.revokeObjectURL(url); + document.getElementById('export-dialog-overlay').remove(); + showUINotification(`Mindmap wurde erfolgreich als ${format.toUpperCase()} exportiert.`, 'success'); + }) + .catch(error => { + console.error('Fehler beim Exportieren der Mindmap:', error); + showUINotification('Fehler beim Exportieren der Mindmap.', 'error'); + document.getElementById('export-dialog-overlay').remove(); + }); + }); + }); + + // Import-Funktionalität implementieren + document.getElementById('import-btn').addEventListener('click', function() { + const mindmapId = parseInt("{{ mindmap.id }}"); + + // Dialog für Import-Format anzeigen + const overlay = document.createElement('div'); + overlay.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; + overlay.id = 'import-dialog-overlay'; + + overlay.innerHTML = ` +
+

Mindmap importieren

+
+ + +
+
+ + +
+
+ `; + + document.body.appendChild(overlay); + + // Event-Listener für Import-Buttons + document.getElementById('cancel-import').addEventListener('click', function() { + document.getElementById('import-dialog-overlay').remove(); + }); + + document.getElementById('confirm-import').addEventListener('click', function() { + const fileInput = document.getElementById('import-file'); + + if (!fileInput.files || fileInput.files.length === 0) { + showUINotification('Bitte wählen Sie eine Datei aus.', 'error'); + return; + } + + const file = fileInput.files[0]; + const formData = new FormData(); + formData.append('file', file); + + // API-Anfrage für den Import + fetch(`/api/mindmap/${mindmapId}/import`, { + method: 'POST', + body: formData + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + document.getElementById('import-dialog-overlay').remove(); + showUINotification('Mindmap wurde erfolgreich importiert.', 'success'); + + // Seite nach kurzer Verzögerung neu laden, um die importierten Daten anzuzeigen + setTimeout(() => { + window.location.reload(); + }, 1500); + } else { + showUINotification(`Fehler beim Importieren: ${data.message}`, 'error'); + } + }) + .catch(error => { + console.error('Fehler beim Importieren der Mindmap:', error); + showUINotification('Fehler beim Importieren der Mindmap.', 'error'); + }); + }); + }); + } else { // Fallback, falls mindmapData null ist if(mindmapNameH1) mindmapNameH1.textContent = "Mindmap nicht gefunden";