Enhance footer layout and mindmap functionality: Revamp footer structure with improved grid layout, add social media icons, and implement a newsletter subscription form. Update mindmap template to use SVG background, streamline script loading, and enhance visualization initialization with new event handlers for user interactions.

This commit is contained in:
2025-04-27 06:33:01 +02:00
parent e46264b201
commit d42c43db50
3 changed files with 423 additions and 266 deletions

230
website/models.py Normal file
View File

@@ -0,0 +1,230 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
from enum import Enum
db = SQLAlchemy()
# Beziehungstypen für Gedankenverknüpfungen
class RelationType(Enum):
SUPPORTS = "stützt"
CONTRADICTS = "widerspricht"
BUILDS_UPON = "baut auf auf"
GENERALIZES = "verallgemeinert"
SPECIFIES = "spezifiziert"
INSPIRES = "inspiriert"
# Beziehungstabelle für viele-zu-viele Beziehung zwischen MindMapNodes
node_relationship = db.Table('node_relationship',
db.Column('parent_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
db.Column('child_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True)
)
# Beziehungstabelle für öffentliche Knoten und Gedanken
node_thought_association = db.Table('node_thought_association',
db.Column('node_id', db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True),
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
)
# Beziehungstabelle für Benutzer-spezifische Mindmap-Knoten und Gedanken
user_mindmap_thought_association = db.Table('user_mindmap_thought_association',
db.Column('user_mindmap_id', db.Integer, db.ForeignKey('user_mindmap.id'), primary_key=True),
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True)
)
# Beziehungstabelle für Benutzer-Bookmarks von Gedanken
user_thought_bookmark = db.Table('user_thought_bookmark',
db.Column('user_id', db.Integer, db.ForeignKey('user.id'), primary_key=True),
db.Column('thought_id', db.Integer, db.ForeignKey('thought.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
is_admin = db.Column(db.Boolean, default=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime)
avatar = db.Column(db.String(200))
bio = db.Column(db.Text)
# Beziehungen
thoughts = db.relationship('Thought', backref='author', lazy=True)
comments = db.relationship('Comment', backref='author', lazy=True)
user_mindmaps = db.relationship('UserMindmap', backref='user', lazy=True)
mindmap_notes = db.relationship('MindmapNote', backref='user', lazy=True)
bookmarked_thoughts = db.relationship('Thought', secondary=user_thought_bookmark,
backref=db.backref('bookmarked_by', lazy='dynamic'))
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
class Category(db.Model):
"""Wissenschaftliche Kategorien für die Gliederung der öffentlichen Mindmap"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
description = db.Column(db.Text)
color_code = db.Column(db.String(7)) # Hex color
icon = db.Column(db.String(50))
parent_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=True)
# Beziehungen
children = db.relationship('Category', backref=db.backref('parent', remote_side=[id]))
nodes = db.relationship('MindMapNode', backref='category', lazy=True)
class MindMapNode(db.Model):
"""Öffentliche Mindmap-Knoten, die für alle Benutzer sichtbar sind"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
color_code = db.Column(db.String(7))
icon = db.Column(db.String(50))
is_public = db.Column(db.Boolean, default=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=True)
category_id = db.Column(db.Integer, db.ForeignKey('category.id'), nullable=True)
# Beziehungen für Baumstruktur (mehrere Eltern möglich)
parents = db.relationship(
'MindMapNode',
secondary=node_relationship,
primaryjoin=(node_relationship.c.child_id == id),
secondaryjoin=(node_relationship.c.parent_id == id),
backref=db.backref('children', lazy='dynamic'),
lazy='dynamic'
)
# Beziehungen zu Gedanken
thoughts = db.relationship('Thought',
secondary=node_thought_association,
backref=db.backref('nodes', lazy='dynamic'))
# Beziehung zum Ersteller
created_by = db.relationship('User', backref='created_nodes')
class UserMindmap(db.Model):
"""Benutzerspezifische Mindmap, die vom Benutzer personalisierbar ist"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
description = db.Column(db.Text)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
is_private = db.Column(db.Boolean, default=True)
# Beziehungen zu öffentlichen Knoten
public_nodes = db.relationship('MindMapNode',
secondary='user_mindmap_node',
backref=db.backref('in_user_mindmaps', lazy='dynamic'))
# Beziehungen zu Gedanken
thoughts = db.relationship('Thought',
secondary=user_mindmap_thought_association,
backref=db.backref('in_user_mindmaps', lazy='dynamic'))
# Notizen zu dieser Mindmap
notes = db.relationship('MindmapNote', backref='mindmap', lazy=True)
# Beziehungstabelle für benutzerorientierte Mindmaps und öffentliche Knoten
class UserMindmapNode(db.Model):
"""Speichert die Beziehung zwischen Benutzer-Mindmaps und öffentlichen Knoten inkl. Position"""
user_mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), primary_key=True)
node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), primary_key=True)
x_position = db.Column(db.Float, default=0) # Position X auf der Mindmap
y_position = db.Column(db.Float, default=0) # Position Y auf der Mindmap
scale = db.Column(db.Float, default=1.0) # Größe des Knotens
added_at = db.Column(db.DateTime, default=datetime.utcnow)
class MindmapNote(db.Model):
"""Private Notizen der Benutzer zu ihrer Mindmap"""
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
mindmap_id = db.Column(db.Integer, db.ForeignKey('user_mindmap.id'), nullable=False)
node_id = db.Column(db.Integer, db.ForeignKey('mind_map_node.id'), nullable=True)
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=True)
content = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
color_code = db.Column(db.String(7), default="#FFF59D") # Farbe der Notiz
class Thought(db.Model):
"""Gedanken und Inhalte, die in der Mindmap verknüpft werden können"""
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
content = db.Column(db.Text, nullable=False)
abstract = db.Column(db.Text)
keywords = db.Column(db.String(500))
color_code = db.Column(db.String(7)) # Hex color code
source_type = db.Column(db.String(50)) # PDF, Markdown, Text etc.
branch = db.Column(db.String(100), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Beziehungen
comments = db.relationship('Comment', backref='thought', lazy=True, cascade="all, delete-orphan")
ratings = db.relationship('ThoughtRating', backref='thought', lazy=True)
outgoing_relations = db.relationship(
'ThoughtRelation',
foreign_keys='ThoughtRelation.source_id',
backref='source_thought',
lazy=True
)
incoming_relations = db.relationship(
'ThoughtRelation',
foreign_keys='ThoughtRelation.target_id',
backref='target_thought',
lazy=True
)
@property
def average_rating(self):
if not self.ratings:
return 0
return sum(r.relevance_score for r in self.ratings) / len(self.ratings)
class ThoughtRelation(db.Model):
"""Beziehungen zwischen Gedanken"""
id = db.Column(db.Integer, primary_key=True)
source_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
target_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
relation_type = db.Column(db.Enum(RelationType), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
created_by_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
# Beziehung zum Ersteller
created_by = db.relationship('User', backref='created_relations')
class ThoughtRating(db.Model):
"""Bewertungen von Gedanken durch Benutzer"""
id = db.Column(db.Integer, primary_key=True)
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
relevance_score = db.Column(db.Integer, nullable=False) # 1-5
created_at = db.Column(db.DateTime, default=datetime.utcnow)
__table_args__ = (
db.UniqueConstraint('thought_id', 'user_id', name='unique_thought_rating'),
)
# Beziehung zum Benutzer
user = db.relationship('User', backref='ratings')
class Comment(db.Model):
"""Kommentare zu Gedanken"""
id = db.Column(db.Integer, primary_key=True)
content = db.Column(db.Text, nullable=False)
thought_id = db.Column(db.Integer, db.ForeignKey('thought.id'), nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_modified = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

View File

@@ -734,55 +734,118 @@
<!-- Footer -->
<footer class="mt-12 py-10 transition-colors duration-300 rounded-t-3xl mx-4 sm:mx-6 md:mx-8"
:class="darkMode ? 'bg-gray-900/40 backdrop-blur-md border border-white/10' : 'bg-white/40 backdrop-blur-md border border-gray-200/50'">
:class="darkMode ? 'bg-gray-900/60 backdrop-blur-xl border-t border-white/10' : 'bg-white/60 backdrop-blur-xl border-t border-gray-200/50'">
<div class="container mx-auto px-4">
<div class="flex flex-col md:flex-row justify-between items-center md:items-start">
<div class="mb-8 md:mb-0 text-center md:text-left">
<a href="{{ url_for('index') }}" class="text-2xl font-bold gradient-text inline-block transform transition-transform hover:scale-105">Systades</a>
<p class="mt-3 text-sm max-w-sm"
:class="darkMode ? 'text-gray-400' : 'text-gray-600'">Eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen.</p>
<div class="grid grid-cols-1 md:grid-cols-3 gap-10">
<!-- Logo und Beschreibung -->
<div class="text-center md:text-left flex flex-col">
<a href="{{ url_for('index') }}" class="text-2xl font-bold mb-4 gradient-text inline-block transform transition-transform hover:scale-105">Systades</a>
<p class="mt-2 text-sm max-w-md"
:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
Eine interaktive Plattform zum Visualisieren, Erforschen und Teilen von Wissen und Gedanken in einem strukturierten Format.
</p>
<!-- Social Media Icons -->
<div class="flex items-center space-x-4 mt-6 justify-center md:justify-start">
<a href="#" class="transition-all duration-200 transform hover:scale-110 hover:-translate-y-1"
:class="darkMode ? 'text-purple-400 hover:text-purple-300' : 'text-purple-600 hover:text-purple-500'">
<i class="fab fa-twitter text-xl"></i>
</a>
<a href="#" class="transition-all duration-200 transform hover:scale-110 hover:-translate-y-1"
:class="darkMode ? 'text-purple-400 hover:text-purple-300' : 'text-purple-600 hover:text-purple-500'">
<i class="fab fa-linkedin text-xl"></i>
</a>
<a href="#" class="transition-all duration-200 transform hover:scale-110 hover:-translate-y-1"
:class="darkMode ? 'text-purple-400 hover:text-purple-300' : 'text-purple-600 hover:text-purple-500'">
<i class="fab fa-github text-xl"></i>
</a>
<a href="#" class="transition-all duration-200 transform hover:scale-110 hover:-translate-y-1"
:class="darkMode ? 'text-purple-400 hover:text-purple-300' : 'text-purple-600 hover:text-purple-500'">
<i class="fab fa-discord text-xl"></i>
</a>
</div>
</div>
<div class="flex flex-wrap justify-center md:justify-start space-x-4 sm:space-x-6 mb-8 md:mb-0">
<a href="{{ url_for('impressum') }}"
class="transition-all duration-200 text-sm px-4 py-2.5 rounded-xl"
:class="darkMode ? 'text-gray-400 hover:text-white hover:bg-purple-600/20 hover:shadow-md' : 'text-gray-700 hover:text-gray-900 hover:bg-purple-500/10 hover:shadow-sm'">
<span class="flex items-center">
<i class="fa-solid fa-info-circle mr-2 opacity-70"></i>
Impressum
</span>
</a>
<a href="{{ url_for('datenschutz') }}"
class="transition-all duration-200 text-sm px-4 py-2.5 rounded-xl"
:class="darkMode ? 'text-gray-400 hover:text-white hover:bg-purple-600/20 hover:shadow-md' : 'text-gray-700 hover:text-gray-900 hover:bg-purple-500/10 hover:shadow-sm'">
<span class="flex items-center">
<i class="fa-solid fa-shield-alt mr-2 opacity-70"></i>
Datenschutz
</span>
</a>
<a href="{{ url_for('agb') }}"
class="transition-all duration-200 text-sm px-4 py-2.5 rounded-xl"
:class="darkMode ? 'text-gray-400 hover:text-white hover:bg-purple-600/20 hover:shadow-md' : 'text-gray-700 hover:text-gray-900 hover:bg-purple-500/10 hover:shadow-sm'">
<span class="flex items-center">
<i class="fa-solid fa-file-contract mr-2 opacity-70"></i>
AGB
</span>
</a>
<!-- Links -->
<div class="grid grid-cols-2 gap-8">
<div class="flex flex-col space-y-3">
<h3 class="font-semibold text-lg mb-2"
:class="darkMode ? 'text-white' : 'text-gray-800'">Navigation</h3>
<a href="{{ url_for('index') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Startseite
</a>
<a href="{{ url_for('mindmap') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Mindmap
</a>
{% if current_user.is_authenticated %}
<a href="{{ url_for('profile') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Profil
</a>
<a href="{{ url_for('my_account') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Meine Merkliste
</a>
{% else %}
<a href="{{ url_for('login') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Anmelden
</a>
<a href="{{ url_for('register') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Registrieren
</a>
{% endif %}
</div>
<div class="flex flex-col space-y-3">
<h3 class="font-semibold text-lg mb-2"
:class="darkMode ? 'text-white' : 'text-gray-800'">Rechtliches</h3>
<a href="{{ url_for('impressum') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Impressum
</a>
<a href="{{ url_for('datenschutz') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
Datenschutz
</a>
<a href="{{ url_for('agb') }}" class="text-sm transition-all duration-200"
:class="darkMode ? 'text-gray-300 hover:text-white' : 'text-gray-600 hover:text-gray-900'">
AGB
</a>
</div>
</div>
<!-- Optional: Social Media Links oder andere Elemente -->
{#
<div class="flex space-x-4">
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-twitter"></i></a>
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-linkedin"></i></a>
<a href="#" class="text-gray-400 hover:text-white transition-colors"><i class="fab fa-github"></i></a>
<!-- Newsletter Anmeldung -->
<div class="flex flex-col">
<h3 class="font-semibold text-lg mb-4"
:class="darkMode ? 'text-white' : 'text-gray-800'">Newsletter</h3>
<p class="text-sm mb-4"
:class="darkMode ? 'text-gray-300' : 'text-gray-600'">
Bleibe auf dem Laufenden mit unseren neuesten Funktionen und Updates.
</p>
<form class="flex flex-col space-y-3">
<input type="email" placeholder="Deine E-Mail Adresse"
class="px-4 py-2.5 rounded-xl transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-purple-500"
:class="darkMode ? 'bg-gray-800/80 text-white border border-gray-700 focus:bg-gray-800' : 'bg-white/80 text-gray-800 border border-gray-300 focus:bg-white'" />
<button type="submit"
class="px-4 py-2.5 rounded-xl font-medium transition-all duration-300 bg-gradient-to-r from-purple-600 to-indigo-600 text-white shadow-md hover:shadow-lg hover:-translate-y-0.5">
Abonnieren
</button>
</form>
</div>
#}
</div>
<div class="mt-8 pt-6 text-center text-xs"
:class="darkMode ? 'border-t border-gray-800/50 text-gray-500' : 'border-t border-gray-300/50 text-gray-600'">
&copy; {{ current_year }} Systades. Alle Rechte vorbehalten.
<!-- Untere Linie -->
<div class="mt-10 pt-6 border-t flex flex-col md:flex-row justify-between items-center"
:class="darkMode ? 'border-gray-800/50 text-gray-400' : 'border-gray-300/50 text-gray-600'">
<div class="text-xs md:text-sm mb-3 md:mb-0">
&copy; {{ current_year }} Systades. Alle Rechte vorbehalten.
</div>
<div class="text-xs md:text-sm">
Designed with <i class="fas fa-heart text-pink-500"></i> in Deutschland
</div>
</div>
</div>
</footer>

View File

@@ -516,7 +516,7 @@
left: 0;
width: 100%;
height: 100%;
background-image: url('/static/network-bg.jpg');
background-image: url('/static/network-bg.svg');
background-size: cover;
background-position: center;
opacity: 0.2;
@@ -651,237 +651,101 @@
</div>
{% endblock %}
{% block extra_js %}
<!-- D3.js für die Mindmap-Visualisierung -->
{% block scripts %}
<!-- D3.js Library -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<!-- Tippy.js für verbesserte Tooltips -->
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.6/dist/umd/popper.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/tippy.js@6.3.7/dist/tippy.umd.min.js"></script>
<!-- D3-Erweiterungen für spezifische Effekte -->
<!-- Mindmap scripts -->
<script src="{{ url_for('static', filename='d3-extensions.js') }}"></script>
<!-- Mindmap JS -->
<script src="{{ url_for('static', filename='mindmap.js') }}"></script>
<script src="{{ url_for('static', filename='network-animation.js') }}"></script>
<!-- Initialization Script -->
<script>
// Dynamische Neuronen-Netz-Animation im Hintergrund
document.addEventListener('DOMContentLoaded', function() {
// Animation des neuronalen Netzwerks hinzufügen
const neuralBg = document.querySelector('.neural-universe-bg');
// Neuronenpunkte erstellen
const neuronCount = 100;
for (let i = 0; i < neuronCount; i++) {
const neuron = document.createElement('div');
neuron.className = 'neuron-point';
// Zufällige Position
const posX = Math.random() * 100;
const posY = Math.random() * 100;
const size = Math.random() * 3 + 1;
const animDuration = Math.random() * 50 + 20;
// Styling mit Glasmorphismus
neuron.style.cssText = `
position: absolute;
left: ${posX}%;
top: ${posY}%;
width: ${size}px;
height: ${size}px;
background: rgba(255, 255, 255, ${Math.random() * 0.3 + 0.1});
border-radius: 50%;
filter: blur(${Math.random() * 1}px);
box-shadow: 0 0 ${Math.random() * 10 + 5}px rgba(179, 143, 255, 0.5);
animation: pulse ${animDuration}s infinite alternate ease-in-out;
`;
neuralBg.appendChild(neuron);
document.addEventListener('DOMContentLoaded', function() {
// Inizialize the mindmap visualization
const mindmapContainer = document.getElementById('mindmap-container');
// Options for the visualization
const options = {
width: mindmapContainer.clientWidth,
height: mindmapContainer.clientHeight,
nodeRadius: 22,
selectedNodeRadius: 28,
linkDistance: 150,
chargeStrength: -1000,
centerForce: 0.15,
tooltipEnabled: true,
onNodeClick: function(node) {
console.log('Node clicked:', node);
// Handle node click - could be expanded to display node content
// or fetch more information from the server
}
// Verbindungen zwischen Neuronen erstellen
const connectionCount = 40;
for (let i = 0; i < connectionCount; i++) {
const connection = document.createElement('div');
connection.className = 'neuron-connection';
// Zufällige Position und Rotation für Verbindungen
const posX = Math.random() * 100;
const posY = Math.random() * 100;
const width = Math.random() * 150 + 50;
const height = Math.random() * 1 + 0.5;
const rotation = Math.random() * 360;
const opacity = Math.random() * 0.2 + 0.05;
const animDuration = Math.random() * 20 + 10;
connection.style.cssText = `
position: absolute;
left: ${posX}%;
top: ${posY}%;
width: ${width}px;
height: ${height}px;
background: linear-gradient(90deg, transparent, rgba(179, 143, 255, ${opacity}), transparent);
transform: rotate(${rotation}deg);
animation: flash ${animDuration}s infinite alternate ease-in-out;
opacity: ${opacity};
`;
neuralBg.appendChild(connection);
};
// Create and initialize the visualization
window.mindmap = new MindMapVisualization('#mindmap-container', options);
// Set up UI event handlers
document.getElementById('zoom-in-btn').addEventListener('click', function() {
if (window.mindmap) {
const transform = d3.zoomTransform(window.mindmap.svg.node());
window.mindmap.svg.call(
d3.zoom().transform,
d3.zoomIdentity.scale(transform.k * 1.3)
);
}
// Initialisiere die Mindmap-Visualisierung
const mindmapContainer = document.getElementById('mindmap-container');
const containerWidth = mindmapContainer.clientWidth;
const containerHeight = mindmapContainer.clientHeight;
const mindmap = new MindMapVisualization('#mindmap-container', {
width: containerWidth,
height: containerHeight,
nodeRadius: 22,
selectedNodeRadius: 28,
linkDistance: 160,
chargeStrength: -1200,
centerForce: 0.1,
tooltipEnabled: true,
onNodeClick: function(node) {
console.log('Node clicked:', node);
// Hier können spezifische Aktionen für Knotenklicks definiert werden
}
});
// Event-Listener für Steuerungsbuttons
document.getElementById('zoom-in-btn').addEventListener('click', function() {
// Zoom-In-Funktionalität
const svg = d3.select('#mindmap-container svg');
const currentZoom = d3.zoomTransform(svg.node());
const newScale = currentZoom.k * 1.3;
svg.transition().duration(300).call(
d3.zoom().transform,
d3.zoomIdentity.translate(currentZoom.x, currentZoom.y).scale(newScale)
);
});
document.getElementById('zoom-out-btn').addEventListener('click', function() {
// Zoom-Out-Funktionalität
const svg = d3.select('#mindmap-container svg');
const currentZoom = d3.zoomTransform(svg.node());
const newScale = currentZoom.k / 1.3;
svg.transition().duration(300).call(
d3.zoom().transform,
d3.zoomIdentity.translate(currentZoom.x, currentZoom.y).scale(newScale)
);
});
document.getElementById('center-btn').addEventListener('click', function() {
// Zentrieren-Funktionalität
const svg = d3.select('#mindmap-container svg');
svg.transition().duration(500).call(
d3.zoom().transform,
d3.zoomIdentity.scale(1)
);
});
// Add Thought Button
document.getElementById('add-thought-btn').addEventListener('click', function() {
// Implementierung für das Hinzufügen eines neuen Gedankens
if (mindmap.selectedNode) {
const newNodeName = prompt('Gedanke eingeben:');
if (newNodeName && newNodeName.trim() !== '') {
const newNodeId = 'node_' + Date.now();
const newNode = {
id: newNodeId,
name: newNodeName,
description: 'Neuer Gedanke',
thought_count: 0
};
// Node zur Mindmap hinzufügen
mindmap.nodes.push(newNode);
// Link zum ausgewählten Knoten erstellen
mindmap.links.push({
source: mindmap.selectedNode.id,
target: newNodeId
});
// Mindmap aktualisieren
mindmap.updateVisualization();
}
} else {
alert('Bitte zuerst einen Knoten auswählen, um einen Gedanken hinzuzufügen.');
}
});
// Connect Button
document.getElementById('connect-btn').addEventListener('click', function() {
// Implementierung für das Verbinden von Knoten
if (mindmap.selectedNode && mindmap.mouseoverNode && mindmap.selectedNode !== mindmap.mouseoverNode) {
// Prüfen, ob Verbindung bereits existiert
const existingLink = mindmap.links.find(link =>
(link.source.id === mindmap.selectedNode.id && link.target.id === mindmap.mouseoverNode.id) ||
(link.source.id === mindmap.mouseoverNode.id && link.target.id === mindmap.selectedNode.id)
);
if (!existingLink) {
// Link erstellen
mindmap.links.push({
source: mindmap.selectedNode.id,
target: mindmap.mouseoverNode.id
});
// Mindmap aktualisieren
mindmap.updateVisualization();
} else {
alert('Diese Verbindung existiert bereits.');
}
} else {
alert('Bitte wähle zwei verschiedene Knoten aus, um sie zu verbinden.');
}
});
// Responsive Anpassung bei Fenstergrößenänderung
window.addEventListener('resize', function() {
const newWidth = mindmapContainer.clientWidth;
const newHeight = mindmapContainer.clientHeight;
if (mindmap.svg) {
mindmap.svg
.attr('width', newWidth)
.attr('height', newHeight);
mindmap.width = newWidth;
mindmap.height = newHeight;
// Force-Simulation aktualisieren
if (mindmap.simulation) {
mindmap.simulation
.force('center', d3.forceCenter(newWidth / 2, newHeight / 2))
.restart();
}
}
});
// Aktualisiere das Aussehen von Bookmarks, sobald die Mindmap vollständig geladen ist
setTimeout(() => {
if (mindmap && typeof mindmap.updateAllBookmarkedNodes === 'function') {
mindmap.updateAllBookmarkedNodes();
}
}, 1000);
});
// Animationen für die Hintergrundeffekte
document.head.insertAdjacentHTML('beforeend', `
<style>
@keyframes pulse {
0% { transform: scale(1); opacity: 0.5; }
100% { transform: scale(1.5); opacity: 0.2; }
document.getElementById('zoom-out-btn').addEventListener('click', function() {
if (window.mindmap) {
const transform = d3.zoomTransform(window.mindmap.svg.node());
window.mindmap.svg.call(
d3.zoom().transform,
d3.zoomIdentity.scale(transform.k / 1.3)
);
}
});
document.getElementById('center-btn').addEventListener('click', function() {
if (window.mindmap) {
window.mindmap.svg.call(
d3.zoom().transform,
d3.zoomIdentity
);
}
});
document.getElementById('add-thought-btn').addEventListener('click', function() {
alert('Diese Funktion steht demnächst zur Verfügung.');
});
document.getElementById('connect-btn').addEventListener('click', function() {
alert('Diese Funktion steht demnächst zur Verfügung.');
});
// Handle window resize
let resizeTimeout;
window.addEventListener('resize', function() {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(function() {
if (window.mindmap) {
window.mindmap.width = mindmapContainer.clientWidth;
window.mindmap.height = mindmapContainer.clientHeight;
window.mindmap.svg
.attr('width', window.mindmap.width)
.attr('height', window.mindmap.height)
.attr('viewBox', `0 0 ${window.mindmap.width} ${window.mindmap.height}`);
// Update the center force
window.mindmap.simulation.force('center',
d3.forceCenter(window.mindmap.width / 2, window.mindmap.height / 2)
);
// Restart the simulation
window.mindmap.simulation.alpha(0.3).restart();
}
@keyframes flash {
0% { opacity: 0.02; }
50% { opacity: 0.2; }
100% { opacity: 0.08; }
}
</style>
`);
}, 250);
});
});
</script>
{% endblock %}