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:
230
website/models.py
Normal file
230
website/models.py
Normal 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)
|
||||
@@ -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'">
|
||||
© {{ 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">
|
||||
© {{ 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>
|
||||
|
||||
@@ -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 %}
|
||||
Reference in New Issue
Block a user