mirror of
https://github.com/kevinveenbirkenbach/homepage.veen.world.git
synced 2025-04-28 15:36:54 +02:00
Compare commits
10 Commits
56513230e4
...
6a0db00f24
Author | SHA1 | Date | |
---|---|---|---|
6a0db00f24 | |||
3529749df5 | |||
ae775916b0 | |||
45969feaed | |||
464d307ee8 | |||
4aceb2ed62 | |||
a8a2efd091 | |||
3284684282 | |||
20c4a4809b | |||
898f7479c9 |
12
Dockerfile
12
Dockerfile
@ -1,18 +1,18 @@
|
|||||||
# Basis-Image für Python
|
# Base image for Python
|
||||||
FROM python:slim
|
FROM python:slim
|
||||||
|
|
||||||
# Arbeitsverzeichnis festlegen
|
# Set the working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Abhängigkeiten kopieren und installieren
|
# Copy and install dependencies
|
||||||
COPY app/requirements.txt requirements.txt
|
COPY app/requirements.txt requirements.txt
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
# Anwendungscode kopieren
|
# Copy application code
|
||||||
COPY app/ .
|
COPY app/ .
|
||||||
|
|
||||||
# Port freigeben
|
# Expose port
|
||||||
EXPOSE 5000
|
EXPOSE 5000
|
||||||
|
|
||||||
# Startbefehl
|
# Start command
|
||||||
CMD ["python", "app.py"]
|
CMD ["python", "app.py"]
|
||||||
|
@ -40,7 +40,7 @@ Access the application locally at [http://127.0.0.1:5000](http://127.0.0.1:5000)
|
|||||||
docker-compose up --build
|
docker-compose up --build
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Access your portfolio:** Open your browser and navigate to `http://localhost:5000`.
|
4. **Access your portfolio:** Open your browser and navigate to [http://localhost:5000](http://localhost:5000).
|
||||||
|
|
||||||
## Configuration Guide 🔧
|
## Configuration Guide 🔧
|
||||||
|
|
||||||
|
16
app/app.py
16
app/app.py
@ -5,6 +5,7 @@ import hashlib
|
|||||||
import yaml
|
import yaml
|
||||||
from utils.configuration_resolver import ConfigurationResolver
|
from utils.configuration_resolver import ConfigurationResolver
|
||||||
from utils.cache_manager import CacheManager
|
from utils.cache_manager import CacheManager
|
||||||
|
from utils.compute_card_classes import compute_card_classes
|
||||||
|
|
||||||
# Initialize the CacheManager
|
# Initialize the CacheManager
|
||||||
cache_manager = CacheManager()
|
cache_manager = CacheManager()
|
||||||
@ -14,7 +15,6 @@ cache_manager.clear_cache()
|
|||||||
|
|
||||||
def load_config(app):
|
def load_config(app):
|
||||||
"""Load and resolve the configuration."""
|
"""Load and resolve the configuration."""
|
||||||
# Lade die Konfigurationsdatei
|
|
||||||
with open("config.yaml", "r") as f:
|
with open("config.yaml", "r") as f:
|
||||||
config = yaml.safe_load(f)
|
config = yaml.safe_load(f)
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ def load_config(app):
|
|||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
load_config(app)
|
load_config(app)
|
||||||
|
|
||||||
# Hole die Umgebungsvariable FLASK_ENV oder setze einen Standardwert
|
# Get the environment variable FLASK_ENV or set a default value
|
||||||
FLASK_ENV = os.getenv("FLASK_ENV", "production")
|
FLASK_ENV = os.getenv("FLASK_ENV", "production")
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
@ -47,7 +47,17 @@ def reload_config_in_dev():
|
|||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template("pages/index.html.j2", cards=app.config["cards"], company=app.config["company"], navigation=app.config["navigation"], platform=app.config["platform"])
|
cards = app.config["cards"]
|
||||||
|
lg_classes, md_classes = compute_card_classes(cards)
|
||||||
|
return render_template(
|
||||||
|
"pages/index.html.j2",
|
||||||
|
cards=cards,
|
||||||
|
company=app.config["company"],
|
||||||
|
navigation=app.config["navigation"],
|
||||||
|
platform=app.config["platform"],
|
||||||
|
lg_classes=lg_classes,
|
||||||
|
md_classes=md_classes
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(debug=(FLASK_ENV == "development"), host="0.0.0.0", port=5000)
|
app.run(debug=(FLASK_ENV == "development"), host="0.0.0.0", port=5000)
|
||||||
|
@ -205,6 +205,7 @@ cards:
|
|||||||
ensuring agile principles are effectively implemented for sustainable success.
|
ensuring agile principles are effectively implemented for sustainable success.
|
||||||
url: https://www.agile-coach.world
|
url: https://www.agile-coach.world
|
||||||
link_text: www.agile-coach.world
|
link_text: www.agile-coach.world
|
||||||
|
iframe: true
|
||||||
- icon:
|
- icon:
|
||||||
source: https://cloud.veen.world/s/logo_personal_coach_512x512/download
|
source: https://cloud.veen.world/s/logo_personal_coach_512x512/download
|
||||||
title: Personal Coach
|
title: Personal Coach
|
||||||
@ -645,4 +646,5 @@ navigation:
|
|||||||
icon:
|
icon:
|
||||||
class: fa-solid fa-scale-balanced
|
class: fa-solid fa-scale-balanced
|
||||||
url: https://s.veen.world/imprint
|
url: https://s.veen.world/imprint
|
||||||
|
iframe: true
|
||||||
|
|
31
app/static/css/custom_scrollbar.css
Normal file
31
app/static/css/custom_scrollbar.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/* Set the scroll container to only scroll vertically */
|
||||||
|
.scroll-container {
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
/* Hide native scrollbar */
|
||||||
|
scrollbar-width: none; /* Firefox */
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-container::-webkit-scrollbar {
|
||||||
|
display: none; /* WebKit */
|
||||||
|
}
|
||||||
|
|
||||||
|
#custom-scrollbar {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 8px;
|
||||||
|
/* height: 100vh; <-- remove or adjust this line */
|
||||||
|
background: transparent;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The scrollbar thumb */
|
||||||
|
#scroll-thumb {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
@ -96,3 +96,15 @@ div#navbarNavheader li.nav-item{
|
|||||||
div#navbarNavfooter li.nav-item {
|
div#navbarNavfooter li.nav-item {
|
||||||
margin-right: 6px;
|
margin-right: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
position: relative;
|
||||||
|
box-shadow:
|
||||||
|
/* Inner shadow */
|
||||||
|
inset 10px 0 10px -10px rgba(0, 0, 0, 0.3), /* Left inner shadow */
|
||||||
|
inset -10px 0 10px -10px rgba(0, 0, 0, 0.3), /* Right inner shadow */
|
||||||
|
/* Outer shadow */
|
||||||
|
10px 0 10px -10px rgba(0, 0, 0, 0.3), /* Right outer shadow */
|
||||||
|
-10px 0 10px -10px rgba(0, 0, 0, 0.3); /* Left outer shadow */
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
@ -1,20 +1,30 @@
|
|||||||
/* Top-Level Dropdown-Menü */
|
/* Top-level dropdown menu */
|
||||||
.nav-item .dropdown-menu {
|
.nav-item .dropdown-menu {
|
||||||
position: absolute; /* Wichtig für Positionierung */
|
position: absolute; /* Important for positioning */
|
||||||
top: 100%; /* Standardmäßige Öffnung nach unten */
|
top: 100%; /* Default opening direction: downwards */
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 1050; /* Damit das Menü über anderen Elementen liegt */
|
z-index: 1050; /* Ensures the menu appears above other elements */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Submenu-Position */
|
/* Submenu position */
|
||||||
.dropdown-submenu > .dropdown-menu {
|
.dropdown-submenu > .dropdown-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 100%; /* Öffnen nach rechts */
|
left: 100%; /* Opens to the right */
|
||||||
z-index: 1050;
|
z-index: 1050;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sicherstellen, dass der Übergang smooth ist */
|
/* Ensure a smooth transition */
|
||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
transition: all 0.3s ease-in-out;
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav.navbar.menu-header {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav.navbar.menu-footer {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
108
app/static/js/custom_scrollbar.js
Normal file
108
app/static/js/custom_scrollbar.js
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
function adjustScrollContainerHeight() {
|
||||||
|
const mainEl = document.getElementById('main');
|
||||||
|
const scrollContainer = mainEl.querySelector('.scroll-container');
|
||||||
|
const scrollbarContainer = document.getElementById('custom-scrollbar');
|
||||||
|
const container = mainEl.parentElement;
|
||||||
|
|
||||||
|
let siblingsHeight = 0;
|
||||||
|
Array.from(container.children).forEach(child => {
|
||||||
|
if(child !== mainEl && child !== scrollbarContainer) {
|
||||||
|
const style = window.getComputedStyle(child);
|
||||||
|
const height = child.offsetHeight;
|
||||||
|
const marginTop = parseFloat(style.marginTop) || 0;
|
||||||
|
const marginBottom = parseFloat(style.marginBottom) || 0;
|
||||||
|
siblingsHeight += height + marginTop + marginBottom;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate the available height for the scroll area
|
||||||
|
const availableHeight = window.innerHeight - siblingsHeight;
|
||||||
|
scrollContainer.style.maxHeight = availableHeight + 'px';
|
||||||
|
scrollContainer.style.overflowY = 'auto';
|
||||||
|
scrollContainer.style.overflowX = 'hidden';
|
||||||
|
|
||||||
|
// Get the current position and height of the scroll container
|
||||||
|
const scrollContainerRect = scrollContainer.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Set the position (top) and height of the custom scrollbar track
|
||||||
|
scrollbarContainer.style.top = scrollContainerRect.top + 'px';
|
||||||
|
scrollbarContainer.style.height = scrollContainerRect.height + 'px';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', adjustScrollContainerHeight);
|
||||||
|
window.addEventListener('resize', adjustScrollContainerHeight);
|
||||||
|
|
||||||
|
// 2. Updates the thumb (size and position) of the custom scrollbar
|
||||||
|
function updateCustomScrollbar() {
|
||||||
|
const scrollContainer = document.querySelector('.scroll-container');
|
||||||
|
const thumb = document.getElementById('scroll-thumb');
|
||||||
|
const customScrollbar = document.getElementById('custom-scrollbar');
|
||||||
|
if (!scrollContainer || !thumb || !customScrollbar) return;
|
||||||
|
|
||||||
|
const contentHeight = scrollContainer.scrollHeight;
|
||||||
|
const containerHeight = scrollContainer.clientHeight;
|
||||||
|
const scrollTop = scrollContainer.scrollTop;
|
||||||
|
|
||||||
|
// Calculate the thumb height (minimum 20px)
|
||||||
|
let thumbHeight = (containerHeight / contentHeight) * containerHeight;
|
||||||
|
thumbHeight = Math.max(thumbHeight, 20);
|
||||||
|
thumb.style.height = thumbHeight + 'px';
|
||||||
|
|
||||||
|
// Calculate the thumb position
|
||||||
|
const maxScrollTop = contentHeight - containerHeight;
|
||||||
|
const maxThumbTop = containerHeight - thumbHeight;
|
||||||
|
const thumbTop = maxScrollTop ? (scrollTop / maxScrollTop) * maxThumbTop : 0;
|
||||||
|
thumb.style.top = thumbTop + 'px';
|
||||||
|
|
||||||
|
// Show the scrollbar if content overflows, otherwise hide it
|
||||||
|
customScrollbar.style.opacity = contentHeight > containerHeight ? '1' : '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the thumb when the container is scrolled
|
||||||
|
const scrollContainer = document.querySelector('.scroll-container');
|
||||||
|
if (scrollContainer) {
|
||||||
|
scrollContainer.addEventListener('scroll', updateCustomScrollbar);
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', updateCustomScrollbar);
|
||||||
|
window.addEventListener('load', updateCustomScrollbar);
|
||||||
|
|
||||||
|
// 3. Interactivity: Enable drag & drop for the scroll thumb
|
||||||
|
let isDragging = false;
|
||||||
|
let dragStartY = 0;
|
||||||
|
let scrollStartY = 0;
|
||||||
|
|
||||||
|
const thumb = document.getElementById('scroll-thumb');
|
||||||
|
|
||||||
|
if (thumb) {
|
||||||
|
thumb.addEventListener('mousedown', function(e) {
|
||||||
|
isDragging = true;
|
||||||
|
dragStartY = e.clientY;
|
||||||
|
scrollStartY = scrollContainer.scrollTop;
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', function(e) {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const containerHeight = scrollContainer.clientHeight;
|
||||||
|
const contentHeight = scrollContainer.scrollHeight;
|
||||||
|
const thumbHeight = thumb.offsetHeight;
|
||||||
|
|
||||||
|
const maxScrollTop = contentHeight - containerHeight;
|
||||||
|
const maxThumbTop = containerHeight - thumbHeight;
|
||||||
|
|
||||||
|
const deltaY = e.clientY - dragStartY;
|
||||||
|
// Calculate the new thumb top position
|
||||||
|
let newThumbTop = (scrollStartY / maxScrollTop) * maxThumbTop + deltaY;
|
||||||
|
newThumbTop = Math.max(0, Math.min(newThumbTop, maxThumbTop));
|
||||||
|
|
||||||
|
// Calculate the new scroll position based on the thumb position
|
||||||
|
const newScrollTop = (newThumbTop / maxThumbTop) * maxScrollTop;
|
||||||
|
scrollContainer.scrollTop = newScrollTop;
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mouseup', function(e) {
|
||||||
|
if (isDragging) {
|
||||||
|
isDragging = false;
|
||||||
|
}
|
||||||
|
});
|
54
app/static/js/iframe.js
Normal file
54
app/static/js/iframe.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const links = document.querySelectorAll(".iframe-link");
|
||||||
|
const mainElement = document.querySelector("main");
|
||||||
|
const container = document.querySelector(".container");
|
||||||
|
const customScrollbar = document.getElementById("custom-scrollbar");
|
||||||
|
|
||||||
|
links.forEach(link => {
|
||||||
|
link.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault(); // Prevent default link behavior
|
||||||
|
|
||||||
|
const url = this.getAttribute("href");
|
||||||
|
|
||||||
|
// Fix the original height of the main element if not already set
|
||||||
|
if (!mainElement.style.height) {
|
||||||
|
mainElement.style.height = `${mainElement.clientHeight}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the container class with container-fluid
|
||||||
|
if (container && !container.classList.contains("container-fluid")) {
|
||||||
|
container.classList.replace("container", "container-fluid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the custom scrollbar
|
||||||
|
if (customScrollbar) {
|
||||||
|
customScrollbar.style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the iframe already exists
|
||||||
|
let iframe = mainElement.querySelector("iframe");
|
||||||
|
|
||||||
|
if (!iframe) {
|
||||||
|
// Create a new iframe
|
||||||
|
iframe = document.createElement("iframe");
|
||||||
|
iframe.width = "100%";
|
||||||
|
iframe.style.border = "none";
|
||||||
|
iframe.style.height = mainElement.style.height; // Apply fixed height
|
||||||
|
iframe.style.overflow = "auto"; // Enable scrollbar inside iframe
|
||||||
|
iframe.scrolling = "auto"; // Ensure scrollability
|
||||||
|
mainElement.innerHTML = ""; // Clear main content
|
||||||
|
mainElement.appendChild(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.src = url; // Load the URL into the iframe
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Adjust iframe height on window resize (optional, to keep it responsive)
|
||||||
|
window.addEventListener("resize", function () {
|
||||||
|
const iframe = mainElement.querySelector("iframe");
|
||||||
|
if (iframe) {
|
||||||
|
iframe.style.height = mainElement.style.height;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -17,15 +17,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Öffnen beim Hovern
|
// Open on hover
|
||||||
item.addEventListener('mouseenter', onMouseEnter);
|
item.addEventListener('mouseenter', onMouseEnter);
|
||||||
|
|
||||||
// Verzögertes Schließen beim Verlassen
|
// Delayed close on mouse leave
|
||||||
item.addEventListener('mouseleave', onMouseLeave);
|
item.addEventListener('mouseleave', onMouseLeave);
|
||||||
|
|
||||||
// Öffnen und Position anpassen beim Klicken
|
// Open and adjust position on click
|
||||||
item.addEventListener('click', (e) => {
|
item.addEventListener('click', (e) => {
|
||||||
e.stopPropagation(); // Verhindert das Schließen von Menüs bei Klick
|
e.stopPropagation(); // Prevents menus from closing when clicking inside
|
||||||
if (item.classList.contains('open')) {
|
if (item.classList.contains('open')) {
|
||||||
closeMenu(item);
|
closeMenu(item);
|
||||||
} else {
|
} else {
|
||||||
@ -44,7 +44,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
addAllMenuEventListeners();
|
addAllMenuEventListeners();
|
||||||
|
|
||||||
// Globale Klick-Listener, um Menüs zu schließen, wenn außerhalb geklickt wird
|
// Global click listener to close menus when clicking outside
|
||||||
document.addEventListener('click', () => {
|
document.addEventListener('click', () => {
|
||||||
[...menuItems, ...subMenuItems].forEach(item => closeMenu(item));
|
[...menuItems, ...subMenuItems].forEach(item => closeMenu(item));
|
||||||
});
|
});
|
||||||
@ -71,7 +71,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isSmallScreen() {
|
function isSmallScreen() {
|
||||||
return window.innerWidth < 992; // Bootstrap-Breakpoint für 'lg'
|
return window.innerWidth < 992; // Bootstrap breakpoint for 'lg'
|
||||||
}
|
}
|
||||||
|
|
||||||
function adjustMenuPosition(submenu, parent, isTopLevel) {
|
function adjustMenuPosition(submenu, parent, isTopLevel) {
|
||||||
@ -89,12 +89,12 @@ function adjustMenuPosition(submenu, parent, isTopLevel) {
|
|||||||
submenu.style.right = '';
|
submenu.style.right = '';
|
||||||
|
|
||||||
if (isTopLevel) {
|
if (isTopLevel) {
|
||||||
if (isSmallScreen && spaceBelow < spaceAbove) {
|
if (isSmallScreen() && spaceBelow < spaceAbove) {
|
||||||
// Für kleine Bildschirme: Menü direkt über dem Eltern-Element öffnen
|
// For small screens: Open menu directly above the parent element
|
||||||
submenu.style.top = 'auto';
|
submenu.style.top = 'auto';
|
||||||
submenu.style.bottom = `${parentRect.height}px`; // Direkt über dem Eltern-Element
|
submenu.style.bottom = `${parentRect.height}px`; // Directly above the parent element
|
||||||
}
|
}
|
||||||
// Top-Level-Menü
|
// Top-level menu
|
||||||
else if (spaceBelow < spaceAbove) {
|
else if (spaceBelow < spaceAbove) {
|
||||||
submenu.style.bottom = `${window.innerHeight - parentRect.bottom - parentRect.height}px`;
|
submenu.style.bottom = `${window.innerHeight - parentRect.bottom - parentRect.height}px`;
|
||||||
submenu.style.top = 'auto';
|
submenu.style.top = 'auto';
|
||||||
@ -103,18 +103,18 @@ function adjustMenuPosition(submenu, parent, isTopLevel) {
|
|||||||
submenu.style.bottom = 'auto';
|
submenu.style.bottom = 'auto';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Submenü
|
// Submenu
|
||||||
const prefersRight = spaceRight >= spaceLeft;
|
const prefersRight = spaceRight >= spaceLeft;
|
||||||
submenu.style.left = prefersRight ? '100%' : 'auto';
|
submenu.style.left = prefersRight ? '100%' : 'auto';
|
||||||
submenu.style.right = prefersRight ? 'auto' : '100%';
|
submenu.style.right = prefersRight ? 'auto' : '100%';
|
||||||
|
|
||||||
// Nach oben öffnen, wenn unten kein Platz ist
|
// Open upwards if there's no space below
|
||||||
if (spaceBelow < spaceAbove) {
|
if (spaceBelow < spaceAbove) {
|
||||||
submenu.style.bottom = `0`;
|
submenu.style.bottom = `0`;
|
||||||
submenu.style.top = `auto`;
|
submenu.style.top = `auto`;
|
||||||
} else {
|
} else {
|
||||||
submenu.style.top = `0`;
|
submenu.style.top = `0`;
|
||||||
submenu.style.bottom = '${parentRect.height}px';
|
submenu.style.bottom = `${parentRect.height}px`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Initialisiert alle Tooltips auf der Seite
|
// Initializes all tooltips on the page
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
|
||||||
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
|
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
|
||||||
|
@ -14,7 +14,8 @@
|
|||||||
<script src="https://kit.fontawesome.com/56f96da298.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/56f96da298.js" crossorigin="anonymous"></script>
|
||||||
<!-- Markdown -->
|
<!-- Markdown -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
<link rel="stylesheet" href="static/css/default.css">
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/default.css') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/custom_scrollbar.css') }}">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -26,7 +27,15 @@
|
|||||||
</header>
|
</header>
|
||||||
{% set menu_type = "header" %}
|
{% set menu_type = "header" %}
|
||||||
{% include "moduls/navigation.html.j2"%}
|
{% include "moduls/navigation.html.j2"%}
|
||||||
|
<main id="main">
|
||||||
|
<div class="scroll-container">
|
||||||
{% block content %}{% endblock %}
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<!-- Custom scrollbar element fixiert am rechten Rand -->
|
||||||
|
<div id="custom-scrollbar">
|
||||||
|
<div id="scroll-thumb"></div>
|
||||||
|
</div>
|
||||||
{% set menu_type = "footer" %}
|
{% set menu_type = "footer" %}
|
||||||
{% include "moduls/navigation.html.j2" %}
|
{% include "moduls/navigation.html.j2" %}
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
@ -43,5 +52,7 @@
|
|||||||
<script src="{{ url_for('static', filename='js/modal.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/modal.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/navigation.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/navigation.js') }}"></script>
|
||||||
<script src="{{ url_for('static', filename='js/tooltip.js') }}"></script>
|
<script src="{{ url_for('static', filename='js/tooltip.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/custom_scrollbar.js') }}"></script>
|
||||||
|
<script src="{{ url_for('static', filename='js/iframe.js') }}"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -12,7 +12,7 @@
|
|||||||
<h3 class="card-title">{{ card.title }}</h3>
|
<h3 class="card-title">{{ card.title }}</h3>
|
||||||
<p class="card-text">{{ card.text }}</p>
|
<p class="card-text">{{ card.text }}</p>
|
||||||
{% if card.url %}
|
{% if card.url %}
|
||||||
<a href="{{ card.url }}" class="mt-auto btn btn-light stretched-link">
|
<a href="{{ card.url }}" class="mt-auto btn btn-light stretched-link {% if card.iframe %}iframe-link{% endif %}">
|
||||||
<i class="fa-solid fa-globe"></i> {{ card.link_text }}
|
<i class="fa-solid fa-globe"></i> {{ card.link_text }}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
<!-- Navigation Bar -->
|
<!-- Navigation Bar -->
|
||||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
<nav class="navbar navbar-expand-lg navbar-light bg-light menu-{{menu_type}}">
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav{{menu_type}}" aria-controls="navbarNav{{menu_type}}" aria-expanded="false" aria-label="Toggle navigation">
|
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav{{menu_type}}" aria-controls="navbarNav{{menu_type}}" aria-expanded="false" aria-label="Toggle navigation">
|
||||||
<span class="navbar-toggler-icon"></span>
|
<span class="navbar-toggler-icon"></span>
|
||||||
@ -46,7 +46,7 @@
|
|||||||
{% if item.url %}
|
{% if item.url %}
|
||||||
<!-- Single Item -->
|
<!-- Single Item -->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link btn btn-light" href="{{ item.url }}" target="{{ item.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ item.description }}">
|
<a class="nav-link btn btn-light {% if item.iframe %}iframe-link{% endif %}" href="{{ item.url }}" target="{{ item.target|default('_blank') }}" data-bs-toggle="tooltip" title="{{ item.description }}">
|
||||||
{{ render_icon_and_name(item) }}
|
{{ render_icon_and_name(item) }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -2,53 +2,10 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
{% set num_cards = cards | length %}
|
|
||||||
{% set lg_classes = [] %}
|
|
||||||
|
|
||||||
{# Compute `lg` column widths: Ensure at least 3 per row unless fewer than 3 exist #}
|
|
||||||
{% if num_cards < 3 %}
|
|
||||||
{% if num_cards == 2 %}
|
|
||||||
{% set lg_classes = ["col-lg-6", "col-lg-6"] %}
|
|
||||||
{% else %}
|
|
||||||
{% set lg_classes = ["col-lg-12"] %}
|
|
||||||
{% endif %}
|
|
||||||
{% elif num_cards % 4 == 0 %}
|
|
||||||
{% set lg_classes = ["col-lg-3"] * num_cards %}
|
|
||||||
{% elif num_cards % 3 == 0 %}
|
|
||||||
{% set lg_classes = ["col-lg-4"] * num_cards %}
|
|
||||||
{% elif num_cards % 2 == 0 %}
|
|
||||||
{% set lg_classes = ["col-lg-6"] * num_cards %}
|
|
||||||
{% else %}
|
|
||||||
{# Distribute for complex cases (e.g., 5, 7, 11) while ensuring at least 3 per row #}
|
|
||||||
{% for i in range(num_cards) %}
|
|
||||||
{% if num_cards % 4 == 3 %}
|
|
||||||
{% if i < 3 %}
|
|
||||||
{% set _ = lg_classes.append("col-lg-4") %}
|
|
||||||
{% else %}
|
|
||||||
{% set _ = lg_classes.append("col-lg-3") %}
|
|
||||||
{% endif %}
|
|
||||||
{% elif num_cards % 4 == 1 %}
|
|
||||||
{% if i < 2 %}
|
|
||||||
{% set _ = lg_classes.append("col-lg-6") %}
|
|
||||||
{% elif i < 5 %}
|
|
||||||
{% set _ = lg_classes.append("col-lg-4") %}
|
|
||||||
{% else %}
|
|
||||||
{% set _ = lg_classes.append("col-lg-3") %}
|
|
||||||
{% endif %}
|
|
||||||
{% elif num_cards % 3 == 2 %}
|
|
||||||
{% if i < 2 %}
|
|
||||||
{% set _ = lg_classes.append("col-lg-6") %}
|
|
||||||
{% else %}
|
|
||||||
{% set _ = lg_classes.append("col-lg-4") %}
|
|
||||||
{% endif %}
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% for card in cards %}
|
{% for card in cards %}
|
||||||
{% set index = loop.index0 %} {# Index starts at 0 #}
|
{% set index = loop.index0 %}
|
||||||
{% set lg_class = lg_classes[index] %}
|
{% set lg_class = lg_classes[index] %}
|
||||||
{% set md_class = "col-md-6" if num_cards % 2 == 0 or index < num_cards - 1 else "col-md-12" %}
|
{% set md_class = md_classes[index] %}
|
||||||
{% include "moduls/card.html.j2" %}
|
{% include "moduls/card.html.j2" %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
42
app/utils/compute_card_classes.py
Normal file
42
app/utils/compute_card_classes.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
def compute_card_classes(cards):
|
||||||
|
num_cards = len(cards)
|
||||||
|
lg_classes = []
|
||||||
|
if num_cards < 3:
|
||||||
|
if num_cards == 2:
|
||||||
|
lg_classes = ["col-lg-6", "col-lg-6"]
|
||||||
|
else:
|
||||||
|
lg_classes = ["col-lg-12"]
|
||||||
|
elif num_cards % 4 == 0:
|
||||||
|
lg_classes = ["col-lg-3"] * num_cards
|
||||||
|
elif num_cards % 3 == 0:
|
||||||
|
lg_classes = ["col-lg-4"] * num_cards
|
||||||
|
elif num_cards % 2 == 0:
|
||||||
|
lg_classes = ["col-lg-6"] * num_cards
|
||||||
|
else:
|
||||||
|
# For complex cases (e.g., 5, 7, 11) – Ensure at least 3 per row
|
||||||
|
for i in range(num_cards):
|
||||||
|
if num_cards % 4 == 3:
|
||||||
|
if i < 3:
|
||||||
|
lg_classes.append("col-lg-4")
|
||||||
|
else:
|
||||||
|
lg_classes.append("col-lg-3")
|
||||||
|
elif num_cards % 4 == 1:
|
||||||
|
if i < 2:
|
||||||
|
lg_classes.append("col-lg-6")
|
||||||
|
elif i < 5:
|
||||||
|
lg_classes.append("col-lg-4")
|
||||||
|
else:
|
||||||
|
lg_classes.append("col-lg-3")
|
||||||
|
elif num_cards % 3 == 2:
|
||||||
|
if i < 2:
|
||||||
|
lg_classes.append("col-lg-6")
|
||||||
|
else:
|
||||||
|
lg_classes.append("col-lg-4")
|
||||||
|
# md classes: If the number of cards is even or if not the last card, otherwise "col-md-12"
|
||||||
|
md_classes = []
|
||||||
|
for i in range(num_cards):
|
||||||
|
if num_cards % 2 == 0 or i < num_cards - 1:
|
||||||
|
md_classes.append("col-md-6")
|
||||||
|
else:
|
||||||
|
md_classes.append("col-md-12")
|
||||||
|
return lg_classes, md_classes
|
65
test.html
Normal file
65
test.html
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Iframe Navigation</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
}
|
||||||
|
#iframe-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
nav {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<a href="https://www.example.com" class="iframe-link">Beispiel 1</a> |
|
||||||
|
<a href="https://www.wikipedia.org" class="iframe-link">Wikipedia</a> |
|
||||||
|
<a href="https://www.openstreetmap.org" class="iframe-link">OpenStreetMap</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main id="main">
|
||||||
|
<p>Hier wird der Inhalt durch ein Iframe ersetzt.</p>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const links = document.querySelectorAll(".iframe-link");
|
||||||
|
const main = document.getElementById("main");
|
||||||
|
|
||||||
|
links.forEach(link => {
|
||||||
|
link.addEventListener("click", function (event) {
|
||||||
|
event.preventDefault(); // Verhindert das Standardverhalten
|
||||||
|
|
||||||
|
const url = this.getAttribute("href");
|
||||||
|
|
||||||
|
// Prüfe, ob das Iframe bereits existiert
|
||||||
|
let iframe = document.getElementById("iframe-container");
|
||||||
|
|
||||||
|
if (!iframe) {
|
||||||
|
// Neues Iframe erstellen
|
||||||
|
iframe = document.createElement("iframe");
|
||||||
|
iframe.id = "iframe-container";
|
||||||
|
iframe.width = "100%";
|
||||||
|
iframe.height = "600px";
|
||||||
|
iframe.style.border = "none";
|
||||||
|
main.innerHTML = ""; // Inhalt von main löschen
|
||||||
|
main.appendChild(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe.src = url; // Lade die URL in das Iframe
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user