glance/internal/assets/static/main.js
2024-05-14 16:38:54 +01:00

193 lines
5.4 KiB
JavaScript

function throttledDebounce(callback, maxDebounceTimes, debounceDelay) {
let debounceTimeout;
let timesDebounced = 0;
return function () {
if (timesDebounced == maxDebounceTimes) {
clearTimeout(debounceTimeout);
timesDebounced = 0;
callback();
return;
}
clearTimeout(debounceTimeout);
timesDebounced++;
debounceTimeout = setTimeout(() => {
timesDebounced = 0;
callback();
}, debounceDelay);
};
};
async function fetchPageContents (pageSlug) {
// TODO: handle non 200 status codes/time outs
// TODO: add retries
const response = await fetch(`/api/pages/${pageSlug}/content/`);
const content = await response.text();
return content;
}
function setupCarousels() {
const carouselElements = document.getElementsByClassName("carousel-container");
for (let i = 0; i < carouselElements.length; i++) {
const carousel = carouselElements[i];
carousel.classList.add("show-right-cutoff");
const itemsContainer = carousel.getElementsByClassName("carousel-items-container")[0];
const determineSideCutoffs = () => {
if (itemsContainer.scrollLeft != 0) {
carousel.classList.add("show-left-cutoff");
} else {
carousel.classList.remove("show-left-cutoff");
}
if (Math.ceil(itemsContainer.scrollLeft) + itemsContainer.clientWidth < itemsContainer.scrollWidth) {
carousel.classList.add("show-right-cutoff");
} else {
carousel.classList.remove("show-right-cutoff");
}
}
const determineSideCutoffsRateLimited = throttledDebounce(determineSideCutoffs, 20, 100);
itemsContainer.addEventListener("scroll", determineSideCutoffsRateLimited);
document.addEventListener("resize", determineSideCutoffsRateLimited);
determineSideCutoffs();
}
}
const minuteInSeconds = 60;
const hourInSeconds = minuteInSeconds * 60;
const dayInSeconds = hourInSeconds * 24;
const monthInSeconds = dayInSeconds * 30;
const yearInSeconds = monthInSeconds * 12;
function relativeTimeSince(timestamp) {
const delta = Math.round((Date.now() / 1000) - timestamp);
if (delta < minuteInSeconds) {
return "1m";
}
if (delta < hourInSeconds) {
return Math.floor(delta / minuteInSeconds) + "m";
}
if (delta < dayInSeconds) {
return Math.floor(delta / hourInSeconds) + "h";
}
if (delta < monthInSeconds) {
return Math.floor(delta / dayInSeconds) + "d";
}
if (delta < yearInSeconds) {
return Math.floor(delta / monthInSeconds) + "mo";
}
return Math.floor(delta / yearInSeconds) + "y";
}
function updateRelativeTimeForElements(elements)
{
for (let i = 0; i < elements.length; i++)
{
const element = elements[i];
const timestamp = element.dataset.dynamicRelativeTime;
if (timestamp === undefined)
continue
element.innerText = relativeTimeSince(timestamp);
}
}
function setupDynamicRelativeTime() {
const elements = document.querySelectorAll("[data-dynamic-relative-time]");
const updateInterval = 60 * 1000;
let lastUpdateTime = Date.now();
const updateElementsAndTimestamp = () => {
updateRelativeTimeForElements(elements);
lastUpdateTime = Date.now();
};
const scheduleRepeatingUpdate = () => setInterval(updateElementsAndTimestamp, updateInterval);
if (document.hidden === undefined) {
scheduleRepeatingUpdate();
return;
}
let timeout = scheduleRepeatingUpdate();
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
clearTimeout(timeout);
return;
}
const delta = Date.now() - lastUpdateTime;
if (delta >= updateInterval) {
updateElementsAndTimestamp();
timeout = scheduleRepeatingUpdate();
return;
}
timeout = setTimeout(() => {
updateElementsAndTimestamp();
timeout = scheduleRepeatingUpdate();
}, updateInterval - delta);
});
}
function setupLazyImages() {
const images = document.querySelectorAll("img[loading=lazy]");
if (images.length == 0) {
return;
}
function imageFinishedTransition(image) {
image.classList.add("finished-transition");
}
for (let i = 0; i < images.length; i++) {
const image = images[i];
if (image.complete) {
image.classList.add("cached");
setTimeout(() => imageFinishedTransition(image), 5);
} else {
// TODO: also handle error event
image.addEventListener("load", () => {
image.classList.add("loaded");
setTimeout(() => imageFinishedTransition(image), 500);
});
}
}
}
async function setupPage() {
const pageElement = document.getElementById("page");
const pageContents = await fetchPageContents(pageData.slug);
pageElement.innerHTML = pageContents;
setTimeout(() => {
document.body.classList.add("animate-element-transition");
}, 200);
setTimeout(setupLazyImages, 5);
setupCarousels();
setupDynamicRelativeTime();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", setupPage);
} else {
setupPage();
}