From f5745b9e81ab22688c0e49c075af0c3752534550 Mon Sep 17 00:00:00 2001 From: michivonah Date: Tue, 18 Mar 2025 21:20:24 +0100 Subject: [PATCH] implement dynamic project loading from api --- i18n/de.toml | 3 +- i18n/en.toml | 3 +- layouts/partials/projects.html | 37 ++---------- static/main.js | 100 ++++++++++++++++++++++++++++++++- static/style.css | 61 ++++++++++++++++++-- 5 files changed, 162 insertions(+), 42 deletions(-) diff --git a/i18n/de.toml b/i18n/de.toml index b05d615..da7f7c6 100644 --- a/i18n/de.toml +++ b/i18n/de.toml @@ -9,4 +9,5 @@ logo = 'Logo' gotop = 'Nach oben!' godown = 'Nach unten!' language = 'Sprache' -select-language = 'Sprache auswählen' \ No newline at end of file +select-language = 'Sprache auswählen' +load-more = 'Mehr laden' \ No newline at end of file diff --git a/i18n/en.toml b/i18n/en.toml index a950ae9..e7752f6 100644 --- a/i18n/en.toml +++ b/i18n/en.toml @@ -9,4 +9,5 @@ logo = 'Logo' gotop = 'Go to top' godown = 'Scroll down!' language = 'Language' -select-language = 'Select language' \ No newline at end of file +select-language = 'Select language' +load-more = 'Load more' \ No newline at end of file diff --git a/layouts/partials/projects.html b/layouts/partials/projects.html index 6e80cfc..4988101 100644 --- a/layouts/partials/projects.html +++ b/layouts/partials/projects.html @@ -1,5 +1,5 @@
-

Projekte

+

{{ T "projects" }}

{{ range .Site.Data.tags }}
@@ -10,37 +10,8 @@
-
-
-

-
-
- -

Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass

-

14.01.2025

-
-
- -
-
-

-
-
- -

Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass

-

14.01.2025

-
-
- -
-
-

-
-
- -

Mein Fazit: 1 Jahr mit dem Europa-Park ResortPass

-

14.01.2025

-
-
+
+
+
\ No newline at end of file diff --git a/static/main.js b/static/main.js index 34f02b7..6521cdf 100644 --- a/static/main.js +++ b/static/main.js @@ -1,5 +1,5 @@ // Add event listeners -document.addEventListener('DOMContentLoaded', function(){ +document.addEventListener('DOMContentLoaded', async function(){ scrollTopVisibilityUpdate(); updateNavStyle(); calculateAge(".age"); @@ -9,6 +9,9 @@ document.addEventListener('DOMContentLoaded', function(){ languageSelector.addEventListener('change', function(){ window.location.href = languageSelector.value + window.location.hash; }); + + // load projects + loadMoreContent('.project-list', 6); }); window.addEventListener('scroll', function(){ @@ -26,6 +29,10 @@ document.querySelectorAll(".nav-links a").forEach(element => { }); }); +document.querySelector(".project-load-btn").addEventListener('click', function(){ + loadMoreContent('.project-list', 3); +}); + // Generic functions function toggleDisplayByClass(className, displayType){ let items = document.getElementsByClassName(className); @@ -94,4 +101,95 @@ function calculateAge(selector){ } obj.textContent = age; +} + +// load projects from api +// content api: https://api.michivonah.ch/?limit=8&page=1 +async function loadContent(endpoint = "https://api.michivonah.ch", limit = 6, page = 1){ + try{ + constRequestUrl = `${endpoint}/?limit=${limit}&page=${page}`; + const response = await fetch(constRequestUrl); + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const data = await response.json(); + return data; + } + catch(error){ + return console.error({"message":error}); + } +} + +// add projects to page +async function showProjects(wrapperSelector, data){ + const wrapper = document.querySelector(wrapperSelector); + + for (const project of data){ + wrapper.append(await getProjectCard(project)); + } +} + +// create project card +async function getProjectCard(data){ + const title = data.title; + const url = data.url; + const image = data.image; + const date = data.date; + const icon = data.icon; + const category = data.category; + + const dateFormatted = new Date(date).toLocaleDateString("de-DE", { + day: "2-digit", + month: "2-digit", + year: "numeric" + }); + + const container = document.createElement("div"); + container.classList = "project-card"; + container.classList.add(category); + //container.style.backgroundImage = `url(${image})`; + + const cardFirst = document.createElement("div"); + cardFirst.classList = "card-first"; + const emptyText = document.createElement("p"); + cardFirst.appendChild(emptyText); + const cardImage = document.createElement("img"); + cardImage.src = image; + cardFirst.appendChild(cardImage); + + const cardSecond = document.createElement("div"); + cardSecond.classList = "card-second"; + const cardIcon = document.createElement("i"); + cardIcon.classList = icon; + const cardTitle = document.createElement("h2"); + cardTitle.textContent = title; + const cardDate = document.createElement("p"); + cardDate.textContent = dateFormatted; + cardSecond.appendChild(cardIcon); + cardSecond.appendChild(cardTitle); + cardSecond.appendChild(cardDate); + + container.appendChild(cardFirst); + container.appendChild(cardSecond); + + return container; +} + +async function loadMoreContent(wrapperSelector, amount, endpoint = "https://api.michivonah.ch", btnSelector = ".project-load-btn"){ + const wrapper = document.querySelector(wrapperSelector); + const page = ((wrapper.childElementCount <= 0) ? 1 : (wrapper.childElementCount / amount) + 1); + + // just for debugging purposes + //console.log(`children: ${wrapper.childElementCount} limit: ${amount} page: ${page}`); + + // error validation -> only load more content when page num is valid + if (page % 1 == 0){ + await showProjects('.project-list', await loadContent(endpoint, amount, page)); + } + else{ + // hide button if no more content is available + //if (event.target.tagName == "BUTTON") event.target.style.display = "none"; + document.querySelector(btnSelector).style.opacity = 0; + } + } \ No newline at end of file diff --git a/static/style.css b/static/style.css index 7c4cdfd..dd61742 100644 --- a/static/style.css +++ b/static/style.css @@ -19,7 +19,8 @@ body{ --color: #f8f8f8; --font: "Source Sans 3", sans-serif; --baseRadius: 12px; - --baseTransition: all 250ms; + --baseTransition: all var(--baseDuration); + --baseDuration: 250ms; --navSmallHeight: 66px; } @@ -367,9 +368,10 @@ nav.small .nav-links a:last-child:focus{ color: var(--primary); margin: 10px 5px; padding: 5px 16px; + font-size: 1rem; border-radius: var(--baseRadius); transition: var(--baseTransition); - border: 1px solid var(--primary); + border: 2px solid var(--primary); } .tag input{ @@ -381,6 +383,13 @@ nav.small .nav-links a:last-child:focus{ color: var(--color); } +.tag:hover{ + padding: 5px 26px; + background: var(--secondary); + color: var(--color); + border: 2px solid var(--color); +} + .project-list{ padding: 20px 0; display: flex; @@ -391,6 +400,24 @@ nav.small .nav-links a:last-child:focus{ align-items: stretch; } +.project-load-btn{ + padding: 10px 20px; + background: var(--primary); + color: var(--color); + font-size: 1rem; + border: 2px solid transparent; + border-radius: var(--baseRadius); + transition: var(--baseTransition); + opacity: 1; +} + +.project-load-btn:hover{ + padding: 10px 40px; + background: var(--secondary); + border: 2px solid var(--color); + cursor: pointer; +} + /* Card animation: https://uiverse.io/suleymanlaarabidev/perfect-husky-88 */ .project-card{ width: 30%; @@ -398,7 +425,7 @@ nav.small .nav-links a:last-child:focus{ min-height: 250px; margin: 20px 10px; padding: 0 10px; - background-image: url(https://blog.michivonah.ch/content/images/size/w1200/2024/12/ResortPass-Review--1-.jpg); + background: var(--primary); background-size: cover; background-repeat: no-repeat; background-position: center; @@ -406,6 +433,27 @@ nav.small .nav-links a:last-child:focus{ transition: var(--baseTransition); box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.705); overflow: hidden; + position: relative; + animation: fadeIn 400ms linear; +} + +@keyframes fadeIn{ + 0%{ + opacity: 0; + } + + 100%{ + opacity: 1; + } +} + +.card-first img{ + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: auto; + height: 100%; } @media (pointer: fine){ @@ -430,7 +478,7 @@ nav.small .nav-links a:last-child:focus{ transition: var(--baseTransition); opacity: 1; } - + .project-card:hover .card-first{ height: 0; opacity: 0; @@ -480,8 +528,9 @@ nav.small .nav-links a:last-child:focus{ box-shadow: 0px 0px 10px 5px rgba(0, 0, 0, 0.705); } - .card-first{ - display: none; + .card-first img{ + height: calc(100% + 60px); + filter: blur(6px); } .card-second{