Compare commits
7 Commits
e4ab1e1bb5
...
de0f837cfd
| Author | SHA1 | Date | |
|---|---|---|---|
| de0f837cfd | |||
| 1c49ddfb19 | |||
| 46c16e5f01 | |||
| 84667bca00 | |||
| 779449559d | |||
| 721a10e861 | |||
| a431873ca2 |
2
.env
2
.env
@@ -1,2 +1,2 @@
|
||||
SECRET_KEY=eed9298856dc9363cd32778265780d6904ba24e6a6b815a2cc382bcdd767ea7b
|
||||
OPENAI_API_KEY=sk-dein-openai-api-schluessel-hier
|
||||
OPENAI_API_KEY=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
|
||||
|
||||
30
Dockerfile
Normal file
30
Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
# Dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Arbeitsverzeichnis in Container
|
||||
WORKDIR /app
|
||||
|
||||
# Systemabhängigkeiten (falls erforderlich)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends gcc libpq-dev && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# pip auf den neuesten Stand bringen und Requirements installieren
|
||||
COPY requirements.txt ./
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install --no-cache-dir -U -r requirements.txt
|
||||
|
||||
# Anwendungscode kopieren
|
||||
COPY . .
|
||||
|
||||
# Exponiere Port 5000 für Flask
|
||||
EXPOSE 5000
|
||||
|
||||
# Setze Umgebungsvariablen
|
||||
ENV FLASK_APP=app.py
|
||||
ENV FLASK_ENV=production
|
||||
|
||||
# Wenn eine .env im Arbeitsverzeichnis vorhanden ist, wird sie automatisch von Flask geladen
|
||||
|
||||
# Startkommando
|
||||
CMD ["python", "app.py"]
|
||||
Binary file not shown.
Binary file not shown.
30
docker-compose.yml
Normal file
30
docker-compose.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
image: systades_app:latest
|
||||
container_name: systades_app
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "5000:5000"
|
||||
volumes:
|
||||
- .:/app:ro
|
||||
depends_on:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: postgres:15-alpine
|
||||
container_name: systades_db
|
||||
restart: always
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
- db_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5432:5432"
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
22
start.sh
Normal file
22
start.sh
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
# Alte Container stoppen und entfernen
|
||||
if [ $(docker ps -aq --filter "name=systades_app" | wc -l) -gt 0 ]; then
|
||||
docker rm -f systades_app || true
|
||||
fi
|
||||
if [ $(docker ps -aq --filter "name=systades_db" | wc -l) -gt 0 ]; then
|
||||
docker rm -f systades_db || true
|
||||
fi
|
||||
|
||||
# Alte Images löschen
|
||||
docker rmi -f systades_app:latest || true
|
||||
|
||||
# Docker-Compose Setup neu bauen
|
||||
docker-compose build --no-cache
|
||||
|
||||
# Docker-Compose neu starten
|
||||
docker-compose up -d --force-recreate
|
||||
|
||||
# Ausgabe
|
||||
echo "Systades-Anwendung ist jetzt unter http://localhost:5000 erreichbar."
|
||||
@@ -40,8 +40,8 @@
|
||||
--light-bg: #f9fafb;
|
||||
--light-text: #1e293b;
|
||||
--light-heading: #0f172a;
|
||||
--light-primary: #3b82f6;
|
||||
--light-primary-hover: #4f46e5;
|
||||
--light-primary: #7c3aed;
|
||||
--light-primary-hover: #6d28d9;
|
||||
--light-secondary: #6b7280;
|
||||
--light-border: #e5e7eb;
|
||||
--light-card-bg: rgba(255, 255, 255, 0.92);
|
||||
@@ -523,4 +523,232 @@ body:not(.dark) .navbar {
|
||||
background-color: var(--light-navbar-bg);
|
||||
box-shadow: var(--light-shadow);
|
||||
border-bottom: 1px solid var(--light-border);
|
||||
}
|
||||
|
||||
/* Erweiterte Light-Mode-spezifische Stile */
|
||||
body:not(.dark) .glass-effect {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
border: 1px solid rgba(209, 213, 219, 0.3);
|
||||
}
|
||||
|
||||
body:not(.dark) .card {
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
border: 1px solid var(--light-border);
|
||||
box-shadow: var(--light-shadow);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
body:not(.dark) .card:hover {
|
||||
box-shadow: 0 8px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* Light Mode Buttons */
|
||||
body:not(.dark) .btn-primary {
|
||||
background-color: var(--light-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
body:not(.dark) .btn-primary:hover {
|
||||
background-color: var(--light-primary-hover);
|
||||
box-shadow: 0 4px 12px rgba(124, 58, 237, 0.2);
|
||||
}
|
||||
|
||||
body:not(.dark) .btn-secondary {
|
||||
background-color: #f3f4f6;
|
||||
color: var(--light-text);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
body:not(.dark) .btn-secondary:hover {
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
body:not(.dark) .btn-outline {
|
||||
background-color: transparent;
|
||||
color: var(--light-primary);
|
||||
border: 1px solid var(--light-primary);
|
||||
}
|
||||
|
||||
body:not(.dark) .btn-outline:hover {
|
||||
background-color: rgba(124, 58, 237, 0.05);
|
||||
}
|
||||
|
||||
/* Light Mode Formulare */
|
||||
body:not(.dark) input,
|
||||
body:not(.dark) select,
|
||||
body:not(.dark) textarea {
|
||||
background-color: white;
|
||||
border: 1px solid #d1d5db;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
body:not(.dark) input:focus,
|
||||
body:not(.dark) select:focus,
|
||||
body:not(.dark) textarea:focus {
|
||||
border-color: var(--light-primary);
|
||||
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.2);
|
||||
}
|
||||
|
||||
/* Light Mode Navigation */
|
||||
body:not(.dark) .sidebar {
|
||||
background-color: white;
|
||||
border-right: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
body:not(.dark) .sidebar-link {
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
body:not(.dark) .sidebar-link:hover {
|
||||
background-color: #f3f4f6;
|
||||
color: var(--light-primary);
|
||||
}
|
||||
|
||||
body:not(.dark) .sidebar-link.active {
|
||||
background-color: rgba(124, 58, 237, 0.08);
|
||||
color: var(--light-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Light Mode Tabellen */
|
||||
body:not(.dark) table {
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
body:not(.dark) th {
|
||||
background-color: #f9fafb;
|
||||
color: #111827;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
body:not(.dark) tr:nth-child(even) {
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
|
||||
body:not(.dark) tr:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
/* Light Mode Icons */
|
||||
body:not(.dark) .icon {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
body:not(.dark) .icon-primary {
|
||||
color: var(--light-primary);
|
||||
}
|
||||
|
||||
/* Light Mode Alerts/Benachrichtigungen */
|
||||
body:not(.dark) .alert-info {
|
||||
background-color: #eff6ff;
|
||||
border-left: 4px solid #3b82f6;
|
||||
color: #1e40af;
|
||||
}
|
||||
|
||||
body:not(.dark) .alert-success {
|
||||
background-color: #ecfdf5;
|
||||
border-left: 4px solid #10b981;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
body:not(.dark) .alert-warning {
|
||||
background-color: #fffbeb;
|
||||
border-left: 4px solid #f59e0b;
|
||||
color: #92400e;
|
||||
}
|
||||
|
||||
body:not(.dark) .alert-error {
|
||||
background-color: #fef2f2;
|
||||
border-left: 4px solid #ef4444;
|
||||
color: #b91c1c;
|
||||
}
|
||||
|
||||
/* Light Mode Badge */
|
||||
body:not(.dark) .badge {
|
||||
background-color: #e5e7eb;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
body:not(.dark) .badge-primary {
|
||||
background-color: rgba(124, 58, 237, 0.1);
|
||||
color: var(--light-primary);
|
||||
}
|
||||
|
||||
/* Light Mode Mindmap spezifisch */
|
||||
body:not(.dark) #cy {
|
||||
background-color: rgba(255, 255, 255, 0.7);
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
body:not(.dark) .node {
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body:not(.dark) .node:hover,
|
||||
body:not(.dark) .node.selected {
|
||||
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.5), 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body:not(.dark) .edge {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
body:not(.dark) .edge:hover,
|
||||
body:not(.dark) .edge.selected {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Footer im Light Mode */
|
||||
body:not(.dark) footer {
|
||||
background-color: rgba(249, 250, 251, 0.7);
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* Alpine.js Transitions im Light Mode */
|
||||
body:not(.dark) [x-cloak] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Suchfeldstyling im Light Mode */
|
||||
body:not(.dark) .search-container input {
|
||||
background-color: white;
|
||||
border: 1px solid #d1d5db;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
body:not(.dark) .search-container input:focus {
|
||||
border-color: var(--light-primary);
|
||||
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.2);
|
||||
}
|
||||
|
||||
body:not(.dark) .search-results {
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
body:not(.dark) .search-result-item:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
/* Profile und Benutzermenü im Light Mode */
|
||||
body:not(.dark) .avatar {
|
||||
border: 2px solid white;
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
body:not(.dark) .user-dropdown {
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
body:not(.dark) .user-dropdown-item:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
@@ -38,8 +38,9 @@ function initMindmapPage() {
|
||||
console.log('D3 Bibliothek verfügbar:', typeof d3 !== 'undefined');
|
||||
console.log('MindMapVisualization verfügbar:', typeof MindMapVisualization !== 'undefined');
|
||||
|
||||
const mindmapContainer = document.getElementById('mindmap-container');
|
||||
const mindmapContainer = document.getElementById('cy');
|
||||
const thoughtsContainer = document.getElementById('thoughts-container');
|
||||
const nodeDetailsContainer = document.getElementById('node-details');
|
||||
|
||||
if (!mindmapContainer) {
|
||||
console.error('Mindmap-Container nicht gefunden!');
|
||||
@@ -50,35 +51,40 @@ function initMindmapPage() {
|
||||
// Prüfe, ob D3.js geladen ist
|
||||
if (typeof d3 === 'undefined') {
|
||||
console.error('D3.js ist nicht geladen!');
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
if (mindmapContainer) {
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">D3.js konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
|
||||
</div>
|
||||
<p class="text-xl">D3.js konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Prüfe, ob MindMapVisualization definiert ist
|
||||
if (typeof MindMapVisualization === 'undefined') {
|
||||
console.error('MindMapVisualization-Klasse ist nicht definiert!');
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
if (mindmapContainer) {
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">MindMap-Visualisierung konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
|
||||
</div>
|
||||
<p class="text-xl">MindMap-Visualisierung konnte nicht geladen werden. Bitte laden Sie die Seite neu.</p>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Erstelle die Mindmap-Visualisierung
|
||||
let mindmap;
|
||||
try {
|
||||
console.log('Versuche, MindMapVisualization zu erstellen...');
|
||||
const mindmap = new MindMapVisualization('#mindmap-container', {
|
||||
mindmap = new MindMapVisualization('#cy', {
|
||||
height: 600,
|
||||
onNodeClick: handleNodeClick
|
||||
});
|
||||
@@ -89,38 +95,51 @@ function initMindmapPage() {
|
||||
// Lade die Mindmap-Daten
|
||||
mindmap.loadData();
|
||||
console.log('MindMapVisualization erfolgreich erstellt und geladen');
|
||||
|
||||
// Dark Mode Listener registrieren
|
||||
registerDarkModeListener(mindmap);
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der MindMapVisualization:', error);
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
if (mindmapContainer) {
|
||||
mindmapContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">Fehler beim Erstellen der Mindmap-Visualisierung:</p>
|
||||
<p class="text-md mt-2">${error.message}</p>
|
||||
</div>
|
||||
<p class="text-xl">Fehler beim Erstellen der Mindmap-Visualisierung:</p>
|
||||
<p class="text-md mt-2">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Suchfunktion für die Mindmap
|
||||
const searchInput = document.getElementById('mindmap-search');
|
||||
const searchInput = document.getElementById('search-mindmap');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', function(e) {
|
||||
mindmap.filterBySearchTerm(e.target.value);
|
||||
});
|
||||
}
|
||||
|
||||
// UI-Elemente initialisieren
|
||||
initializeMindmapButtons(mindmap);
|
||||
|
||||
/**
|
||||
* Behandelt Klicks auf Mindmap-Knoten
|
||||
*/
|
||||
async function handleNodeClick(node) {
|
||||
// Details im Detailbereich anzeigen
|
||||
if (nodeDetailsContainer) {
|
||||
updateNodeDetails(node);
|
||||
}
|
||||
|
||||
if (!thoughtsContainer) return;
|
||||
|
||||
// Zeige Lade-Animation
|
||||
thoughtsContainer.innerHTML = `
|
||||
<div class="flex justify-center items-center p-12">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-400"></div>
|
||||
<div class="flex justify-center items-center p-4">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-primary-500"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -140,37 +159,110 @@ function initMindmapPage() {
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Gedanken:', error);
|
||||
thoughtsContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-red-500 mb-4">
|
||||
<i class="fa-solid fa-triangle-exclamation text-4xl"></i>
|
||||
<div class="p-4">
|
||||
<div class="text-red-500 mb-2">
|
||||
<i class="fa-solid fa-triangle-exclamation text-xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">Fehler beim Laden der Gedanken.</p>
|
||||
<p class="text-gray-300">Bitte versuchen Sie es später erneut.</p>
|
||||
<p class="text-sm">Fehler beim Laden der Gedanken.</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert den Node-Details-Bereich mit Informationen zum ausgewählten Knoten
|
||||
*/
|
||||
function updateNodeDetails(node) {
|
||||
if (!nodeDetailsContainer) return;
|
||||
|
||||
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||
const textClass = isDarkMode ? 'text-gray-300' : 'text-gray-600';
|
||||
|
||||
nodeDetailsContainer.innerHTML = `
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold mb-2 ${isDarkMode ? 'text-white' : 'text-gray-800'}">
|
||||
${node.name}
|
||||
</h3>
|
||||
<div class="mb-3">
|
||||
<span class="px-2 py-1 text-xs rounded-full ${getCategoryBadgeClass(node.category)}">
|
||||
${node.category || 'Keine Kategorie'}
|
||||
</span>
|
||||
</div>
|
||||
<p class="text-sm ${textClass}">
|
||||
${node.description || 'Keine Beschreibung verfügbar.'}
|
||||
</p>
|
||||
|
||||
<div class="mt-4 text-sm">
|
||||
<div class="flex justify-between items-center mb-1">
|
||||
<span class="${textClass}">Erstellt:</span>
|
||||
<span class="${textClass}">${formatDate(node.created_at || new Date())}</span>
|
||||
</div>
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="${textClass}">Verbindungen:</span>
|
||||
<span class="${textClass}">${node.connections || 0}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatiert ein Datum in ein lesbares Format
|
||||
*/
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('de-DE', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert eine CSS-Klasse für die Kategorie-Badge basierend auf der Kategorie
|
||||
*/
|
||||
function getCategoryBadgeClass(category) {
|
||||
if (!category) return 'bg-gray-500 text-white';
|
||||
|
||||
const categories = {
|
||||
'Konzept': 'bg-blue-500 text-white',
|
||||
'Theorie': 'bg-purple-500 text-white',
|
||||
'Methode': 'bg-green-500 text-white',
|
||||
'Person': 'bg-yellow-500 text-black',
|
||||
'Ereignis': 'bg-red-500 text-white',
|
||||
'Referenz': 'bg-indigo-500 text-white'
|
||||
};
|
||||
|
||||
return categories[category] || 'bg-gray-500 text-white';
|
||||
}
|
||||
|
||||
/**
|
||||
* Rendert die Gedanken in den Container
|
||||
*/
|
||||
function renderThoughts(thoughts, nodeName) {
|
||||
const isDarkMode = document.documentElement.classList.contains('dark');
|
||||
|
||||
// Wenn keine Gedanken vorhanden sind
|
||||
if (thoughts.length === 0) {
|
||||
thoughtsContainer.innerHTML = `
|
||||
<div class="glass-effect p-6 text-center">
|
||||
<div class="text-blue-400 mb-4">
|
||||
<i class="fa-solid fa-info-circle text-4xl"></i>
|
||||
<div class="text-center py-4">
|
||||
<div class="text-blue-400 mb-2">
|
||||
<i class="fa-solid fa-info-circle text-xl"></i>
|
||||
</div>
|
||||
<p class="text-xl">Keine Gedanken für "${nodeName}" vorhanden.</p>
|
||||
<button id="add-thought-btn" class="btn-primary mt-4">
|
||||
<i class="fa-solid fa-plus mr-2"></i> Gedanke hinzufügen
|
||||
<p class="text-sm ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}">
|
||||
Keine Gedanken zu "${nodeName}" vorhanden.
|
||||
</p>
|
||||
<button id="add-thought-btn" class="mt-3 px-3 py-1.5 text-xs rounded-lg transition-colors duration-200"
|
||||
${isDarkMode ?
|
||||
'class="bg-primary-600 hover:bg-primary-700 text-white"' :
|
||||
'class="bg-primary-500 hover:bg-primary-600 text-white"'}>
|
||||
<i class="fa-solid fa-plus mr-1"></i> Gedanke hinzufügen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event-Listener für den Button
|
||||
document.getElementById('add-thought-btn').addEventListener('click', () => {
|
||||
document.getElementById('add-thought-btn')?.addEventListener('click', () => {
|
||||
openAddThoughtModal(nodeName);
|
||||
});
|
||||
|
||||
@@ -179,24 +271,29 @@ function initMindmapPage() {
|
||||
|
||||
// Gedanken anzeigen
|
||||
thoughtsContainer.innerHTML = `
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h2 class="text-xl font-bold text-white">Gedanken zu "${nodeName}"</h2>
|
||||
<button id="add-thought-btn" class="btn-primary">
|
||||
<i class="fa-solid fa-plus mr-2"></i> Neuer Gedanke
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h3 class="text-sm font-semibold ${isDarkMode ? 'text-white' : 'text-gray-800'}">
|
||||
Gedanken zu "${nodeName}"
|
||||
</h3>
|
||||
<button id="add-thought-btn" class="px-2 py-1 text-xs rounded-lg transition-colors duration-200"
|
||||
${isDarkMode ?
|
||||
'class="bg-primary-600 hover:bg-primary-700 text-white"' :
|
||||
'class="bg-primary-500 hover:bg-primary-600 text-white"'}>
|
||||
<i class="fa-solid fa-plus mr-1"></i> Neu
|
||||
</button>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 gap-4" id="thoughts-grid"></div>
|
||||
<div class="space-y-3 max-h-80 overflow-y-auto pr-1" id="thoughts-grid"></div>
|
||||
`;
|
||||
|
||||
// Button-Event-Listener
|
||||
document.getElementById('add-thought-btn').addEventListener('click', () => {
|
||||
document.getElementById('add-thought-btn')?.addEventListener('click', () => {
|
||||
openAddThoughtModal(nodeName);
|
||||
});
|
||||
|
||||
// Gedanken-Karten rendern
|
||||
const thoughtsGrid = document.getElementById('thoughts-grid');
|
||||
thoughts.forEach((thought, index) => {
|
||||
const card = createThoughtCard(thought);
|
||||
const card = createThoughtCard(thought, isDarkMode);
|
||||
|
||||
// Animation verzögern für gestaffeltes Erscheinen
|
||||
setTimeout(() => {
|
||||
@@ -211,567 +308,149 @@ function initMindmapPage() {
|
||||
/**
|
||||
* Erstellt eine Gedanken-Karte
|
||||
*/
|
||||
function createThoughtCard(thought) {
|
||||
function createThoughtCard(thought, isDarkMode) {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card transition-all duration-300 opacity-0 translate-y-4 transform hover:shadow-lg border-l-4';
|
||||
card.style.borderLeftColor = thought.color_code || '#4080ff';
|
||||
card.className = `relative transition-all duration-300 opacity-0 translate-y-4 transform rounded-lg overflow-hidden p-3 border-l-4 ${isDarkMode ? 'bg-gray-800/70' : 'bg-white/90'} border-l-[${thought.color_code || '#4080ff'}]`;
|
||||
|
||||
// Karten-Inhalt
|
||||
card.innerHTML = `
|
||||
<div class="p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<h3 class="text-lg font-bold text-white">${thought.title}</h3>
|
||||
<div class="text-sm text-gray-400">${thought.timestamp}</div>
|
||||
<div class="flex justify-between items-start mb-1">
|
||||
<h4 class="text-sm font-semibold ${isDarkMode ? 'text-white' : 'text-gray-800'}">${thought.title}</h4>
|
||||
<div class="text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}">${formatDate(thought.timestamp)}</div>
|
||||
</div>
|
||||
<div class="prose dark:prose-invert prose-sm">
|
||||
<p class="text-xs line-clamp-2 ${isDarkMode ? 'text-gray-300' : 'text-gray-600'}">${thought.content}</p>
|
||||
</div>
|
||||
${thought.keywords ? `
|
||||
<div class="flex flex-wrap gap-1 mt-2">
|
||||
${thought.keywords.split(',').slice(0, 2).map(keyword =>
|
||||
`<span class="px-2 py-0.5 text-[10px] rounded-full ${isDarkMode ? 'bg-gray-700 text-gray-300' : 'bg-gray-200 text-gray-700'}">${keyword.trim()}</span>`
|
||||
).join('')}
|
||||
${thought.keywords.split(',').length > 2 ?
|
||||
`<span class="px-2 py-0.5 text-[10px] rounded-full ${isDarkMode ? 'bg-gray-700 text-gray-300' : 'bg-gray-200 text-gray-700'}">+${thought.keywords.split(',').length - 2}</span>` :
|
||||
''}
|
||||
</div>
|
||||
<div class="prose dark:prose-invert mt-2">
|
||||
<p>${thought.content}</p>
|
||||
</div>
|
||||
${thought.keywords ? `
|
||||
<div class="flex flex-wrap gap-1 mt-3">
|
||||
${thought.keywords.split(',').map(keyword =>
|
||||
`<span class="px-2 py-1 text-xs rounded-full bg-secondary-700 text-white">${keyword.trim()}</span>`
|
||||
).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="mt-4 flex justify-between items-center">
|
||||
<div class="text-sm text-gray-400">
|
||||
<i class="fa-solid fa-user mr-1"></i> ${thought.author}
|
||||
</div>
|
||||
<div class="flex space-x-2">
|
||||
<button class="text-sm px-2 py-1 rounded hover:bg-white/10 transition-colors"
|
||||
onclick="showComments(${thought.id})">
|
||||
<i class="fa-solid fa-comments mr-1"></i> Kommentare
|
||||
</button>
|
||||
<button class="text-sm px-2 py-1 rounded hover:bg-white/10 transition-colors"
|
||||
onclick="showRelations(${thought.id})">
|
||||
<i class="fa-solid fa-diagram-project mr-1"></i> Beziehungen
|
||||
</button>
|
||||
</div>
|
||||
` : ''}
|
||||
<div class="mt-2 flex justify-between items-center">
|
||||
<div class="text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}">
|
||||
<i class="fa-solid fa-user text-xs mr-1"></i> ${thought.author}
|
||||
</div>
|
||||
<button class="viewThought-btn text-xs px-2 py-1 rounded ${isDarkMode ? 'hover:bg-white/10' : 'hover:bg-gray-200'} transition-colors"
|
||||
data-thought-id="${thought.id}">
|
||||
<i class="fa-solid fa-eye mr-1"></i> Ansehen
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event-Listener für die Detailansicht
|
||||
card.querySelector('.viewThought-btn').addEventListener('click', function() {
|
||||
const thoughtId = this.getAttribute('data-thought-id');
|
||||
showThoughtDetail(thoughtId);
|
||||
});
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet das Modal zum Hinzufügen eines neuen Gedankens
|
||||
* Event-Listener für Dark-Mode-Änderungen registrieren
|
||||
*/
|
||||
function openAddThoughtModal(nodeName) {
|
||||
// Node-Information extrahieren
|
||||
let nodeId, nodeTitle;
|
||||
|
||||
if (typeof nodeName === 'string') {
|
||||
// Wenn nur ein String übergeben wurde
|
||||
nodeTitle = nodeName;
|
||||
// Versuche nodeId aus der Mindmap zu finden
|
||||
const nodeElement = d3.selectAll('.node-group').filter(d => d.name === nodeName);
|
||||
if (nodeElement.size() > 0) {
|
||||
nodeId = nodeElement.datum().id;
|
||||
}
|
||||
} else if (typeof nodeName === 'object') {
|
||||
// Wenn ein Node-Objekt übergeben wurde
|
||||
nodeId = nodeName.id;
|
||||
nodeTitle = nodeName.name;
|
||||
} else {
|
||||
console.error('Ungültiger Node-Parameter', nodeName);
|
||||
return;
|
||||
}
|
||||
|
||||
// Modal-Struktur erstellen
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 z-50 flex items-center justify-center p-4';
|
||||
modal.innerHTML = `
|
||||
<div class="absolute inset-0 bg-black/50 backdrop-blur-sm" id="modal-backdrop"></div>
|
||||
<div class="glass-effect relative rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto z-10 transform transition-all">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold text-white flex items-center">
|
||||
<span class="w-3 h-3 rounded-full bg-primary-400 mr-2"></span>
|
||||
Neuer Gedanke zu "${nodeTitle}"
|
||||
</h3>
|
||||
<button id="close-modal-btn" class="text-gray-400 hover:text-white transition-colors">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
<form id="add-thought-form" class="space-y-4">
|
||||
<input type="hidden" id="node_id" name="node_id" value="${nodeId || ''}">
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-gray-300">Titel</label>
|
||||
<input type="text" id="title" name="title" required
|
||||
class="mt-1 block w-full rounded-md bg-dark-700 border border-dark-500 text-white p-2.5 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label for="content" class="block text-sm font-medium text-gray-300">Inhalt</label>
|
||||
<textarea id="content" name="content" rows="5" required
|
||||
class="mt-1 block w-full rounded-md bg-dark-700 border border-dark-500 text-white p-2.5 focus:ring-2 focus:ring-primary-500 focus:border-transparent"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="keywords" class="block text-sm font-medium text-gray-300">Schlüsselwörter (kommagetrennt)</label>
|
||||
<input type="text" id="keywords" name="keywords"
|
||||
class="mt-1 block w-full rounded-md bg-dark-700 border border-dark-500 text-white p-2.5 focus:ring-2 focus:ring-primary-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label for="abstract" class="block text-sm font-medium text-gray-300">Zusammenfassung (optional)</label>
|
||||
<textarea id="abstract" name="abstract" rows="2"
|
||||
class="mt-1 block w-full rounded-md bg-dark-700 border border-dark-500 text-white p-2.5 focus:ring-2 focus:ring-primary-500 focus:border-transparent"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<label for="color_code" class="block text-sm font-medium text-gray-300">Farbcode</label>
|
||||
<div class="flex space-x-2 mt-1">
|
||||
<input type="color" id="color_code" name="color_code" value="#4080ff"
|
||||
class="h-10 w-10 rounded bg-dark-700 border border-dark-500">
|
||||
<select id="predefined_colors"
|
||||
class="block flex-grow rounded-md bg-dark-700 border border-dark-500 text-white p-2.5">
|
||||
<option value="#4080ff">Blau</option>
|
||||
<option value="#a040ff">Lila</option>
|
||||
<option value="#40bf80">Grün</option>
|
||||
<option value="#ff4080">Rot</option>
|
||||
<option value="#ffaa00">Orange</option>
|
||||
<option value="#00ccff">Türkis</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-between pt-4">
|
||||
<div class="flex items-center">
|
||||
<div class="relative">
|
||||
<button type="button" id="open-relation-btn" class="btn-outline text-sm pl-3 pr-9">
|
||||
<i class="fa-solid fa-diagram-project mr-2"></i> Verbindung
|
||||
<i class="fa-solid fa-chevron-down absolute right-3 top-1/2 transform -translate-y-1/2"></i>
|
||||
</button>
|
||||
<div id="relation-menu" class="absolute left-0 mt-2 w-60 rounded-md shadow-lg bg-dark-800 ring-1 ring-black ring-opacity-5 z-10 hidden">
|
||||
<div class="py-1">
|
||||
<div class="px-3 py-2 text-xs font-semibold text-gray-400 border-b border-dark-600">BEZIEHUNGSTYPEN</div>
|
||||
<div class="max-h-48 overflow-y-auto">
|
||||
<button type="button" class="relation-type-btn w-full text-left px-4 py-2 text-sm text-white hover:bg-dark-600" data-type="supports">
|
||||
<i class="fa-solid fa-circle-arrow-up text-green-400 mr-2"></i> Stützt
|
||||
</button>
|
||||
<button type="button" class="relation-type-btn w-full text-left px-4 py-2 text-sm text-white hover:bg-dark-600" data-type="contradicts">
|
||||
<i class="fa-solid fa-circle-arrow-down text-red-400 mr-2"></i> Widerspricht
|
||||
</button>
|
||||
<button type="button" class="relation-type-btn w-full text-left px-4 py-2 text-sm text-white hover:bg-dark-600" data-type="builds_upon">
|
||||
<i class="fa-solid fa-arrow-right text-blue-400 mr-2"></i> Baut auf auf
|
||||
</button>
|
||||
<button type="button" class="relation-type-btn w-full text-left px-4 py-2 text-sm text-white hover:bg-dark-600" data-type="generalizes">
|
||||
<i class="fa-solid fa-arrow-up-wide-short text-purple-400 mr-2"></i> Verallgemeinert
|
||||
</button>
|
||||
<button type="button" class="relation-type-btn w-full text-left px-4 py-2 text-sm text-white hover:bg-dark-600" data-type="specifies">
|
||||
<i class="fa-solid fa-arrow-down-wide-short text-yellow-400 mr-2"></i> Spezifiziert
|
||||
</button>
|
||||
<button type="button" class="relation-type-btn w-full text-left px-4 py-2 text-sm text-white hover:bg-dark-600" data-type="inspires">
|
||||
<i class="fa-solid fa-lightbulb text-amber-400 mr-2"></i> Inspiriert
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input type="hidden" id="relation_type" name="relation_type" value="">
|
||||
<input type="hidden" id="relation_target" name="relation_target" value="">
|
||||
</div>
|
||||
<div class="flex space-x-3">
|
||||
<button type="button" id="cancel-btn" class="btn-outline">Abbrechen</button>
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fa-solid fa-save mr-2"></i> Speichern
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Focus auf das erste Feld setzen
|
||||
setTimeout(() => {
|
||||
modal.querySelector('#title').focus();
|
||||
}, 100);
|
||||
|
||||
// Event-Listener hinzufügen
|
||||
modal.querySelector('#modal-backdrop').addEventListener('click', closeModal);
|
||||
modal.querySelector('#close-modal-btn').addEventListener('click', closeModal);
|
||||
modal.querySelector('#cancel-btn').addEventListener('click', closeModal);
|
||||
|
||||
// Farbauswahl-Event-Listener
|
||||
const colorInput = modal.querySelector('#color_code');
|
||||
const predefinedColors = modal.querySelector('#predefined_colors');
|
||||
|
||||
predefinedColors.addEventListener('change', function() {
|
||||
colorInput.value = this.value;
|
||||
});
|
||||
|
||||
// Beziehungsmenü-Funktionalität
|
||||
const relationBtn = modal.querySelector('#open-relation-btn');
|
||||
const relationMenu = modal.querySelector('#relation-menu');
|
||||
|
||||
relationBtn.addEventListener('click', function() {
|
||||
relationMenu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// Klick außerhalb des Menüs schließt es
|
||||
document.addEventListener('click', function(event) {
|
||||
if (!relationBtn.contains(event.target) && !relationMenu.contains(event.target)) {
|
||||
relationMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// Beziehungstyp-Auswahl
|
||||
const relationTypeBtns = modal.querySelectorAll('.relation-type-btn');
|
||||
const relationTypeInput = modal.querySelector('#relation_type');
|
||||
|
||||
relationTypeBtns.forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const relationType = this.dataset.type;
|
||||
relationTypeInput.value = relationType;
|
||||
|
||||
// Sichtbare Anzeige aktualisieren
|
||||
relationBtn.innerHTML = `
|
||||
<i class="fa-solid fa-diagram-project mr-2"></i>
|
||||
${this.innerText.trim()}
|
||||
<i class="fa-solid fa-chevron-down absolute right-3 top-1/2 transform -translate-y-1/2"></i>
|
||||
`;
|
||||
|
||||
// Menü schließen
|
||||
relationMenu.classList.add('hidden');
|
||||
});
|
||||
});
|
||||
|
||||
// Form-Submit-Handler
|
||||
const form = modal.querySelector('#add-thought-form');
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
function registerDarkModeListener(mindmap) {
|
||||
document.addEventListener('darkModeToggled', function(event) {
|
||||
const isDark = event.detail.isDark;
|
||||
console.log('Dark mode changed to:', isDark);
|
||||
|
||||
const formData = new FormData(form);
|
||||
const thoughtData = {
|
||||
node_id: formData.get('node_id'),
|
||||
title: formData.get('title'),
|
||||
content: formData.get('content'),
|
||||
keywords: formData.get('keywords'),
|
||||
abstract: formData.get('abstract'),
|
||||
color_code: formData.get('color_code'),
|
||||
relation_type: formData.get('relation_type'),
|
||||
relation_target: formData.get('relation_target')
|
||||
};
|
||||
// Mindmap-Styling aktualisieren
|
||||
if (mindmap && mindmap.updateColorScheme) {
|
||||
mindmap.updateColorScheme(isDark);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/thoughts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(thoughtData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Speichern des Gedankens.');
|
||||
// UI-Elemente aktualisieren, falls notwendig
|
||||
if (nodeDetailsContainer) {
|
||||
const selectedNode = mindmap.getSelectedNode();
|
||||
if (selectedNode) {
|
||||
updateNodeDetails(selectedNode);
|
||||
}
|
||||
|
||||
// Modal schließen
|
||||
closeModal();
|
||||
|
||||
// Gedanken neu laden
|
||||
if (nodeId) {
|
||||
handleNodeClick({ id: nodeId, name: nodeTitle });
|
||||
}
|
||||
|
||||
// Erfolgsbenachrichtigung
|
||||
if (window.MindMap && window.MindMap.showNotification) {
|
||||
window.MindMap.showNotification('Gedanke erfolgreich gespeichert.', 'success');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern:', error);
|
||||
if (window.MindMap && window.MindMap.showNotification) {
|
||||
window.MindMap.showNotification('Fehler beim Speichern des Gedankens.', 'error');
|
||||
}
|
||||
|
||||
if (thoughtsContainer) {
|
||||
const thoughts = thoughtsContainer.querySelector('#thoughts-grid');
|
||||
if (thoughts) {
|
||||
// Einfache Aktualisierung - im Produktionscode würde man das komplexer machen
|
||||
const selectedNode = mindmap.getSelectedNode();
|
||||
if (selectedNode) {
|
||||
handleNodeClick(selectedNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Modal schließen
|
||||
function closeModal() {
|
||||
modal.classList.add('opacity-0');
|
||||
setTimeout(() => {
|
||||
modal.remove();
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert die Buttons für die Mindmap
|
||||
*/
|
||||
function initializeMindmapButtons(mindmap) {
|
||||
// Zoom-Buttons
|
||||
document.getElementById('zoomIn')?.addEventListener('click', () => mindmap.zoomIn());
|
||||
document.getElementById('zoomOut')?.addEventListener('click', () => mindmap.zoomOut());
|
||||
document.getElementById('zoomReset')?.addEventListener('click', () => mindmap.zoomReset());
|
||||
|
||||
// Layout-Button
|
||||
document.getElementById('reLayout')?.addEventListener('click', () => mindmap.reLayout());
|
||||
|
||||
// Export-Button
|
||||
document.getElementById('exportMindmap')?.addEventListener('click', () => mindmap.exportMindmap());
|
||||
|
||||
// Bearbeitungs-Buttons
|
||||
document.getElementById('addNode')?.addEventListener('click', () => openAddNodeModal(mindmap));
|
||||
document.getElementById('addEdge')?.addEventListener('click', () => mindmap.startAddEdgeMode());
|
||||
document.getElementById('editNode')?.addEventListener('click', () => {
|
||||
const selectedNode = mindmap.getSelectedNode();
|
||||
if (selectedNode) {
|
||||
openEditNodeModal(selectedNode, mindmap);
|
||||
} else {
|
||||
showNotification('Bitte wählen Sie zuerst einen Knoten aus.', 'warning');
|
||||
}
|
||||
});
|
||||
document.getElementById('deleteNode')?.addEventListener('click', () => {
|
||||
const selectedNode = mindmap.getSelectedNode();
|
||||
if (selectedNode) {
|
||||
confirmDeleteNode(selectedNode, mindmap);
|
||||
} else {
|
||||
showNotification('Bitte wählen Sie zuerst einen Knoten aus.', 'warning');
|
||||
}
|
||||
});
|
||||
document.getElementById('deleteEdge')?.addEventListener('click', () => {
|
||||
const selectedEdge = mindmap.getSelectedEdge();
|
||||
if (selectedEdge) {
|
||||
confirmDeleteEdge(selectedEdge, mindmap);
|
||||
} else {
|
||||
showNotification('Bitte wählen Sie zuerst eine Verbindung aus.', 'warning');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Zeigt eine Benachrichtigung an
|
||||
*/
|
||||
function showNotification(message, type = 'info') {
|
||||
console.log(`Benachrichtigung (${type}): ${message}`);
|
||||
// Implementiere eine Toast-Benachrichtigung
|
||||
// ...
|
||||
}
|
||||
|
||||
// ... Weitere Funktionen wie openAddThoughtModal, openAddNodeModal usw. ...
|
||||
}
|
||||
|
||||
/**
|
||||
* Füge globale Funktionen für das Mindmap-Objekt hinzu
|
||||
* Öffnet das Modal zum Hinzufügen eines Gedankens
|
||||
*/
|
||||
window.showComments = async function(thoughtId) {
|
||||
try {
|
||||
// Lade-Animation erstellen
|
||||
const modal = createModalWithLoading('Kommentare werden geladen...');
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Kommentare laden
|
||||
const response = await fetch(`/api/comments/${thoughtId}`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const comments = await response.json();
|
||||
|
||||
// Modal mit Kommentaren aktualisieren
|
||||
updateModalWithComments(modal, comments, thoughtId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Kommentare:', error);
|
||||
if (window.MindMap && window.MindMap.showNotification) {
|
||||
window.MindMap.showNotification('Fehler beim Laden der Kommentare.', 'error');
|
||||
} else {
|
||||
alert('Fehler beim Laden der Kommentare.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Zeigt die Beziehungen eines Gedankens an
|
||||
*/
|
||||
window.showRelations = async function(thoughtId) {
|
||||
try {
|
||||
// Lade-Animation erstellen
|
||||
const modal = createModalWithLoading('Beziehungen werden geladen...');
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Beziehungen laden
|
||||
const response = await fetch(`/api/thoughts/${thoughtId}/relations`);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const relations = await response.json();
|
||||
|
||||
// Modal mit Beziehungen aktualisieren
|
||||
updateModalWithRelations(modal, relations, thoughtId);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden der Beziehungen:', error);
|
||||
if (window.MindMap && window.MindMap.showNotification) {
|
||||
window.MindMap.showNotification('Fehler beim Laden der Beziehungen.', 'error');
|
||||
} else {
|
||||
alert('Fehler beim Laden der Beziehungen.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Erstellt ein Modal mit Lade-Animation
|
||||
*/
|
||||
function createModalWithLoading(loadingText) {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'fixed inset-0 z-50 flex items-center justify-center p-4';
|
||||
modal.innerHTML = `
|
||||
<div class="absolute inset-0 bg-black/50" id="modal-backdrop"></div>
|
||||
<div class="glass-effect relative rounded-lg shadow-xl max-w-2xl w-full max-h-[90vh] overflow-y-auto z-10">
|
||||
<div class="p-6 text-center">
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-t-2 border-b-2 border-primary-400"></div>
|
||||
</div>
|
||||
<p class="text-lg text-white">${loadingText}</p>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event-Listener zum Schließen
|
||||
modal.querySelector('#modal-backdrop').addEventListener('click', () => {
|
||||
modal.remove();
|
||||
});
|
||||
|
||||
return modal;
|
||||
function openAddThoughtModal(nodeName) {
|
||||
// Modal-Implementierung...
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert das Modal mit Kommentaren
|
||||
* Zeigt die Detailansicht eines Gedankens
|
||||
*/
|
||||
function updateModalWithComments(modal, comments, thoughtId) {
|
||||
const modalContent = modal.querySelector('.glass-effect');
|
||||
|
||||
modalContent.innerHTML = `
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold text-white">Kommentare</h3>
|
||||
<button id="close-modal-btn" class="text-gray-400 hover:text-white">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="comments-list mb-6 space-y-4">
|
||||
${comments.length === 0 ?
|
||||
'<div class="text-center text-gray-400 py-4">Keine Kommentare vorhanden.</div>' :
|
||||
comments.map(comment => `
|
||||
<div class="glass-effect p-3 rounded">
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="font-medium text-white">${comment.author}</div>
|
||||
<div class="text-xs text-gray-400">${comment.timestamp}</div>
|
||||
</div>
|
||||
<p class="mt-2 text-gray-200">${comment.content}</p>
|
||||
</div>
|
||||
`).join('')
|
||||
}
|
||||
</div>
|
||||
|
||||
<form id="comment-form" class="space-y-3">
|
||||
<div>
|
||||
<label for="comment-content" class="block text-sm font-medium text-gray-300">Neuer Kommentar</label>
|
||||
<textarea id="comment-content" name="content" rows="3" required
|
||||
class="mt-1 block w-full rounded-md bg-dark-700 border-dark-500 text-white"></textarea>
|
||||
</div>
|
||||
<div class="flex justify-end pt-2">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fa-solid fa-paper-plane mr-2"></i> Senden
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event-Listener hinzufügen
|
||||
modalContent.querySelector('#close-modal-btn').addEventListener('click', () => {
|
||||
modal.remove();
|
||||
});
|
||||
|
||||
// Kommentar-Formular
|
||||
const form = modalContent.querySelector('#comment-form');
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const content = form.elements.content.value;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/comments', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
thought_id: thoughtId,
|
||||
content: content
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Speichern des Kommentars.');
|
||||
}
|
||||
|
||||
// Modal schließen
|
||||
modal.remove();
|
||||
|
||||
// Erfolgsbenachrichtigung
|
||||
MindMap.showNotification('Kommentar erfolgreich gespeichert.', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern des Kommentars:', error);
|
||||
MindMap.showNotification('Fehler beim Speichern des Kommentars.', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert das Modal mit Beziehungen
|
||||
*/
|
||||
function updateModalWithRelations(modal, relations, thoughtId) {
|
||||
const modalContent = modal.querySelector('.glass-effect');
|
||||
|
||||
modalContent.innerHTML = `
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<h3 class="text-xl font-bold text-white">Beziehungen</h3>
|
||||
<button id="close-modal-btn" class="text-gray-400 hover:text-white">
|
||||
<i class="fa-solid fa-xmark text-xl"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="relations-list mb-6 space-y-4">
|
||||
${relations.length === 0 ?
|
||||
'<div class="text-center text-gray-400 py-4">Keine Beziehungen vorhanden.</div>' :
|
||||
relations.map(relation => `
|
||||
<div class="glass-effect p-3 rounded">
|
||||
<div class="flex items-center">
|
||||
<span class="inline-block px-2 py-1 rounded-full text-xs font-medium bg-primary-600 text-white">
|
||||
${relation.relation_type}
|
||||
</span>
|
||||
<div class="ml-3">
|
||||
<div class="text-white">Ziel: Gedanke #${relation.target_id}</div>
|
||||
<div class="text-xs text-gray-400">Erstellt von ${relation.created_by} am ${relation.created_at}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')
|
||||
}
|
||||
</div>
|
||||
|
||||
<form id="relation-form" class="space-y-3">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label for="target_id" class="block text-sm font-medium text-gray-300">Ziel-Gedanke ID</label>
|
||||
<input type="number" id="target_id" name="target_id" required
|
||||
class="mt-1 block w-full rounded-md bg-dark-700 border-dark-500 text-white">
|
||||
</div>
|
||||
<div>
|
||||
<label for="relation_type" class="block text-sm font-medium text-gray-300">Beziehungstyp</label>
|
||||
<select id="relation_type" name="relation_type" required
|
||||
class="mt-1 block w-full rounded-md bg-dark-700 border-dark-500 text-white">
|
||||
<option value="SUPPORTS">Stützt</option>
|
||||
<option value="CONTRADICTS">Widerspricht</option>
|
||||
<option value="BUILDS_UPON">Baut auf auf</option>
|
||||
<option value="GENERALIZES">Verallgemeinert</option>
|
||||
<option value="SPECIFIES">Spezifiziert</option>
|
||||
<option value="INSPIRES">Inspiriert</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-end pt-2">
|
||||
<button type="submit" class="btn-primary">
|
||||
<i class="fa-solid fa-plus mr-2"></i> Beziehung erstellen
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Event-Listener hinzufügen
|
||||
modalContent.querySelector('#close-modal-btn').addEventListener('click', () => {
|
||||
modal.remove();
|
||||
});
|
||||
|
||||
// Beziehungs-Formular
|
||||
const form = modalContent.querySelector('#relation-form');
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = {
|
||||
source_id: thoughtId,
|
||||
target_id: parseInt(form.elements.target_id.value),
|
||||
relation_type: form.elements.relation_type.value
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/relations', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(formData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Erstellen der Beziehung.');
|
||||
}
|
||||
|
||||
// Modal schließen
|
||||
modal.remove();
|
||||
|
||||
// Erfolgsbenachrichtigung
|
||||
MindMap.showNotification('Beziehung erfolgreich erstellt.', 'success');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Erstellen der Beziehung:', error);
|
||||
MindMap.showNotification('Fehler beim Erstellen der Beziehung.', 'error');
|
||||
}
|
||||
});
|
||||
function showThoughtDetail(thoughtId) {
|
||||
// Detailansicht-Implementierung...
|
||||
}
|
||||
@@ -69,8 +69,8 @@
|
||||
<link href="{{ url_for('static', filename='fonts/inter.css') }}" rel="stylesheet">
|
||||
<link href="{{ url_for('static', filename='fonts/jetbrains-mono.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- Icons - Self-hosted Font Awesome -->
|
||||
<link href="{{ url_for('static', filename='css/all.min.css') }}" rel="stylesheet">
|
||||
<!-- Font Awesome vom CDN -->
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" rel="stylesheet">
|
||||
|
||||
<!-- Assistent CSS -->
|
||||
<link href="{{ url_for('static', filename='css/assistant.css') }}" rel="stylesheet">
|
||||
@@ -111,7 +111,7 @@
|
||||
<!-- Seitenspezifische Styles -->
|
||||
{% block extra_css %}{% endblock %}
|
||||
|
||||
<!-- Custom dark mode styles -->
|
||||
<!-- Custom dark/light mode styles -->
|
||||
<!-- ► ► Farb‑Token strikt getrennt ◄ ◄ -->
|
||||
<style>
|
||||
/* Light‑Mode */
|
||||
@@ -149,6 +149,39 @@
|
||||
.glass-navbar { @apply glass-morphism border backdrop-blur-xl; }
|
||||
.light .glass-navbar { background-color:rgba(255,255,255,.8); border-color:rgba(0,0,0,.05); }
|
||||
.dark .glass-navbar { background-color:rgba(10,14,25,.8); border-color:rgba(255,255,255,.05); }
|
||||
|
||||
/* Light-Mode spezifische Stile */
|
||||
body:not(.dark) {
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-link-light {
|
||||
color: var(--text-secondary);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-link-light:hover {
|
||||
color: var(--text-primary);
|
||||
background-color: rgba(126, 34, 206, 0.1);
|
||||
}
|
||||
|
||||
.nav-link-light-active {
|
||||
color: var(--accent-primary);
|
||||
background-color: rgba(126, 34, 206, 0.15);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Kartendesign im Light-Mode */
|
||||
body:not(.dark) .card {
|
||||
background-color: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
body:not(.dark) .card:hover {
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark bg-gray-900 text-white" x-data="{
|
||||
@@ -168,6 +201,7 @@
|
||||
if (data.success) {
|
||||
this.darkMode = data.darkMode === 'true';
|
||||
document.querySelector('html').classList.toggle('dark', this.darkMode);
|
||||
document.querySelector('body').classList.toggle('dark', this.darkMode);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
@@ -178,6 +212,7 @@
|
||||
toggleDarkMode() {
|
||||
this.darkMode = !this.darkMode;
|
||||
document.querySelector('html').classList.toggle('dark', this.darkMode);
|
||||
document.querySelector('body').classList.toggle('dark', this.darkMode);
|
||||
|
||||
fetch('/api/set_dark_mode', {
|
||||
method: 'POST',
|
||||
|
||||
@@ -1,234 +1,287 @@
|
||||
<!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>
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Mindmap{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
/* Spezifische Stile für die Mindmap-Seite */
|
||||
#cy {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
background-color: var(--bg-secondary);
|
||||
transition: background-color 0.3s ease;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
<!-- Cytoscape.js -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
|
||||
.mindmap-container {
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
<!-- Socket.IO -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
|
||||
.dark .mindmap-container {
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
<!-- Feather Icons (optional) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
|
||||
.mindmap-toolbar {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--bg-secondary);
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
body:not(.dark) .mindmap-toolbar {
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
border-radius: 0.375rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.category-filters {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
padding: 0.75rem;
|
||||
background-color: var(--bg-secondary);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
/* Kontextmenü */
|
||||
#context-menu {
|
||||
position: absolute;
|
||||
border-radius: 0.375rem;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dark #context-menu {
|
||||
background-color: #232837;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
body:not(.dark) #context-menu {
|
||||
background-color: white;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#context-menu .menu-item {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.dark #context-menu .menu-item:hover {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
body:not(.dark) #context-menu .menu-item:hover {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Zusätzliches Layout */
|
||||
.mindmap-section {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.mindmap-section {
|
||||
grid-template-columns: 2fr 1fr;
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="mb-8">
|
||||
<h1 class="text-3xl font-bold mb-2 gradient-text">Interaktive Mindmap</h1>
|
||||
<p class="text-gray-500 dark:text-gray-400">Visualisieren und erkunden Sie Wissensnetze und Gedankenkonstrukte</p>
|
||||
</div>
|
||||
|
||||
<!-- Unsere Mindmap JS -->
|
||||
<script src="{{ url_for('static', filename='js/mindmap.js') }}"></script>
|
||||
|
||||
<!-- Icons initialisieren -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (typeof feather !== 'undefined') {
|
||||
feather.replace();
|
||||
}
|
||||
<div class="mindmap-section">
|
||||
<!-- Linke Spalte: Mindmap -->
|
||||
<div class="mindmap-container">
|
||||
<!-- Suchleiste -->
|
||||
<div class="mindmap-toolbar">
|
||||
<div class="flex-1">
|
||||
<input type="text" id="search-mindmap"
|
||||
class="w-full max-w-md px-3 py-2 rounded-lg"
|
||||
x-bind:class="darkMode ? 'bg-gray-800 border border-gray-700 text-white' : 'bg-white border border-gray-300 text-gray-700'"
|
||||
placeholder="In der Mindmap suchen...">
|
||||
</div>
|
||||
|
||||
<button id="addNode" class="btn"
|
||||
x-bind:class="darkMode ? 'bg-primary-600 hover:bg-primary-700 text-white' : 'bg-primary-500 hover:bg-primary-600 text-white'">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
<span class="hidden sm:inline">Knoten hinzufügen</span>
|
||||
</button>
|
||||
|
||||
<button id="addEdge" class="btn"
|
||||
x-bind:class="darkMode ? 'bg-primary-600 hover:bg-primary-700 text-white' : 'bg-primary-500 hover:bg-primary-600 text-white'">
|
||||
<i class="fa-solid fa-link"></i>
|
||||
<span class="hidden sm:inline">Verbindung</span>
|
||||
</button>
|
||||
|
||||
<button id="reLayout" class="btn"
|
||||
x-bind:class="darkMode ? 'bg-gray-700 hover:bg-gray-600 text-white' : 'bg-gray-200 hover:bg-gray-300 text-gray-700'">
|
||||
<i class="fa-solid fa-compass-drafting"></i>
|
||||
<span class="hidden sm:inline">Anordnen</span>
|
||||
</button>
|
||||
|
||||
<div class="relative" x-data="{ open: false }">
|
||||
<button @click="open = !open" class="btn"
|
||||
x-bind:class="darkMode ? 'bg-gray-700 hover:bg-gray-600 text-white' : 'bg-gray-200 hover:bg-gray-300 text-gray-700'">
|
||||
<i class="fa-solid fa-ellipsis-vertical"></i>
|
||||
</button>
|
||||
|
||||
<div x-show="open" @click.away="open = false"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
class="absolute right-0 mt-2 w-48 origin-top-right z-10 rounded-md shadow-lg"
|
||||
x-bind:class="darkMode ? 'bg-gray-800' : 'bg-white'">
|
||||
<div class="py-1">
|
||||
<button id="editNode" class="block w-full text-left px-4 py-2 text-sm hover:bg-gray-700"
|
||||
x-bind:class="darkMode ? 'text-gray-300 hover:bg-gray-700' : 'text-gray-700 hover:bg-gray-100'">
|
||||
<i class="fa-solid fa-edit mr-2"></i>Knoten bearbeiten
|
||||
</button>
|
||||
<button id="deleteNode" class="block w-full text-left px-4 py-2 text-sm hover:bg-red-600/20"
|
||||
x-bind:class="darkMode ? 'text-gray-300 hover:bg-red-600/20' : 'text-gray-700 hover:bg-red-100'">
|
||||
<i class="fa-solid fa-trash-alt mr-2"></i>Knoten löschen
|
||||
</button>
|
||||
<button id="deleteEdge" class="block w-full text-left px-4 py-2 text-sm hover:bg-red-600/20"
|
||||
x-bind:class="darkMode ? 'text-gray-300 hover:bg-red-600/20' : 'text-gray-700 hover:bg-red-100'">
|
||||
<i class="fa-solid fa-unlink mr-2"></i>Verbindung löschen
|
||||
</button>
|
||||
<button id="exportMindmap" class="block w-full text-left px-4 py-2 text-sm"
|
||||
x-bind:class="darkMode ? 'text-gray-300 hover:bg-gray-700' : 'text-gray-700 hover:bg-gray-100'">
|
||||
<i class="fa-solid fa-file-export mr-2"></i>Exportieren
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Kategoriefilter -->
|
||||
<div id="category-filters" class="category-filters"></div>
|
||||
|
||||
<!-- Mindmap-Visualisierung -->
|
||||
<div id="cy"></div>
|
||||
</div>
|
||||
|
||||
<!-- Rechte Spalte: Detail-Ansicht und Interaktionen -->
|
||||
<div class="space-y-6">
|
||||
<!-- Knoteninformationen -->
|
||||
<div class="card p-5"
|
||||
x-bind:class="darkMode ? 'bg-gray-800/80 border border-gray-700' : 'bg-white/90 border border-gray-200'">
|
||||
<h2 class="text-xl font-semibold mb-3"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
||||
<i class="fa-solid fa-circle-info mr-2"></i>Information
|
||||
</h2>
|
||||
<div id="node-details">
|
||||
<p class="text-sm"
|
||||
x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
Wählen Sie einen Knoten aus der Mindmap aus, um Details anzuzeigen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Verbundene Gedanken -->
|
||||
<div class="card p-5"
|
||||
x-bind:class="darkMode ? 'bg-gray-800/80 border border-gray-700' : 'bg-white/90 border border-gray-200'">
|
||||
<div class="flex justify-between items-center mb-3">
|
||||
<h2 class="text-xl font-semibold"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
||||
<i class="fa-solid fa-lightbulb mr-2"></i>Gedanken
|
||||
</h2>
|
||||
<button class="btn-sm"
|
||||
x-bind:class="darkMode ? 'bg-primary-600 hover:bg-primary-700 text-white' : 'bg-primary-500 hover:bg-primary-600 text-white'">
|
||||
<i class="fa-solid fa-plus mr-1"></i>Neu
|
||||
</button>
|
||||
</div>
|
||||
<div id="thoughts-container">
|
||||
<p class="text-sm"
|
||||
x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
Wählen Sie einen Knoten aus, um zugehörige Gedanken zu sehen.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Schnellhilfe -->
|
||||
<div class="card p-5"
|
||||
x-bind:class="darkMode ? 'bg-gray-800/80 border border-gray-700' : 'bg-white/90 border border-gray-200'">
|
||||
<h2 class="text-xl font-semibold mb-3"
|
||||
x-bind:class="darkMode ? 'text-white' : 'text-gray-800'">
|
||||
<i class="fa-solid fa-circle-question mr-2"></i>Schnellhilfe
|
||||
</h2>
|
||||
<ul class="text-sm space-y-2"
|
||||
x-bind:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
|
||||
<li><i class="fa-solid fa-mouse mr-2"></i>Klicken Sie auf einen Knoten, um ihn auszuwählen</li>
|
||||
<li><i class="fa-solid fa-arrows-up-down-left-right mr-2"></i>Ziehen Sie die Maus, um die Ansicht zu verschieben</li>
|
||||
<li><i class="fa-solid fa-magnifying-glass-plus mr-2"></i>Mausrad zum Zoomen</li>
|
||||
<li><i class="fa-solid fa-right-click mr-2"></i>Rechtsklick für Kontextmenü</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Cytoscape.js für die Mindmap-Visualisierung -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.26.0/cytoscape.min.js"></script>
|
||||
|
||||
<!-- Socket.IO für Echtzeitaktualisierungen -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.7.2/socket.io.min.js"></script>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Verarbeite Dark-Mode-Umschaltungen für die Mindmap-Visualisierung
|
||||
document.addEventListener('darkModeToggled', function(event) {
|
||||
// Hier könnten wir das Mindmap-Styling basierend auf Dark/Light-Mode aktualisieren
|
||||
// z.B. Hintergrundfarben, Knotenfarben etc.
|
||||
console.log('Dark Mode für Mindmap aktualisiert:', event.detail.isDark);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
// Weitere Mindmap-spezifische Initialisierung
|
||||
// Sollte mit dem vorhandenen mindmap.js-Modul funktionieren
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,58 +0,0 @@
|
||||
@echo off
|
||||
echo Mindmap Projekt - Windows Setup
|
||||
echo ==============================
|
||||
echo.
|
||||
|
||||
REM Prüfen, ob Python installiert ist
|
||||
python --version >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo Python ist nicht installiert oder nicht im PATH.
|
||||
echo Bitte installiere Python 3.11 von https://www.python.org/downloads/
|
||||
echo und stelle sicher, dass "Add Python to PATH" während der Installation aktiviert ist.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Erstelle virtuelle Umgebung...
|
||||
python -m venv venv
|
||||
if %errorlevel% neq 0 (
|
||||
echo Fehler beim Erstellen der virtuellen Umgebung.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Aktiviere virtuelle Umgebung...
|
||||
call venv\Scripts\activate.bat
|
||||
if %errorlevel% neq 0 (
|
||||
echo Fehler beim Aktivieren der virtuellen Umgebung.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Aktualisiere pip...
|
||||
python -m pip install --upgrade pip
|
||||
if %errorlevel% neq 0 (
|
||||
echo Warnung: Pip konnte nicht aktualisiert werden. Fahre trotzdem fort.
|
||||
)
|
||||
|
||||
echo Installiere Abhängigkeiten...
|
||||
pip install -r requirements.txt
|
||||
if %errorlevel% neq 0 (
|
||||
echo Fehler beim Installieren der Abhängigkeiten.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Setup abgeschlossen!
|
||||
echo.
|
||||
echo Zum Starten des Servers:
|
||||
echo 1. Führe "venv\Scripts\activate.bat" aus
|
||||
echo 2. Führe "python TOOLS.py db:rebuild" aus (Nur beim ersten Mal oder zum Zurücksetzen der Datenbank)
|
||||
echo 3. Führe "python TOOLS.py user:admin" aus (Erstellt einen Admin-Benutzer: admin/admin)
|
||||
echo 4. Führe "python TOOLS.py server:run" aus
|
||||
echo.
|
||||
echo Die Anwendung ist dann unter http://localhost:5000 erreichbar.
|
||||
echo.
|
||||
|
||||
pause
|
||||
Reference in New Issue
Block a user