Files
godot-website/assets/js/priorities.mjs
2024-12-02 21:44:47 +01:00

211 lines
4.9 KiB
JavaScript

import { gsap } from "./modules/gsap@3.12.5.min.mjs";
// If we need to add a search bar, use this next line:
// import Fuse from "./modules/fuse.js@7.0.0.min.mjs";
// =====================
// Blank target to links
// =====================
/**
* Adds `target="_blank"` to element links. This will open links in a new window.
* @returns {void}
*/
function addBlankTargetToElementLinks() {
for (const anchor of document.querySelectorAll(".element-content a")) {
if (!(anchor instanceof HTMLAnchorElement)) {
continue;
}
anchor.target = "_blank";
}
}
// =============
// Scroll to top
// =============
/**
* Setups scroll to top functionality.
* @returns {void}
*/
function setupScrollToTop() {
const navElement = document.querySelector("#priorities-navigation");
const scrollToTopElement = document.querySelector("#scroll-to-top");
let scrollToTopTween = null;
let scrollState = "";
const showScrollToTop = () => {
if (scrollState === "show") {
return;
}
scrollState = "show";
if (scrollToTopTween != null) {
scrollToTopTween.kill();
}
scrollToTopElement.style.display = "block";
scrollToTopTween = gsap.to(scrollToTopElement, {
opacity: 1,
duration: 0.5,
});
};
const hideScrollToTop = () => {
if (scrollState === "hide") {
return;
}
scrollState = "hide";
if (scrollToTopTween != null) {
scrollToTopTween.kill();
}
scrollToTopTween = gsap.to(scrollToTopElement, {
opacity: 0,
duration: 0.5,
onComplete: () => {
scrollToTopElement.style.display = "none";
}
});
};
const scrollToTopObserver = new IntersectionObserver((entries, observer) => {
const entry = entries[0];
if (entry.isIntersecting) {
hideScrollToTop();
} else {
const rect = navElement.getBoundingClientRect();
if (rect.y > window.innerHeight) {
hideScrollToTop();
} else {
showScrollToTop();
}
}
});
scrollToTopObserver.observe(navElement);
}
// ============================
// Open details selected in URL
// ============================
/**
* If there's a hash to the URL, make sure that the appropritate details tag is open.
* @returns {void}
*/
function openDetailsSelectedInUrl() {
if (window.location.hash === "") {
return;
}
/** @type {HTMLDetailsElement | null} */
const element = document.querySelector(`${window.location.hash} details.element-main`);
if (element == null) {
return;
}
element.open = true;
}
// ========================
// Handle navigation events
// ========================
/**
* Opens the current hash when the navigation changes.
* The API is still quite new, not supported by some browsers. (2024-11-21)
* @returns {void}
*/
function handleNavigateEvent() {
// The API is not yet supported by all browsers.
if (!("Navigation" in window)) {
//openOnHandleClick();
handleHashChangeEvent();
return;
}
/**
* @param {NavigateEvent} event
*/
navigation.addEventListener("navigate", (event) => {
const url = new URL(event.destination.url);
/** @type {HTMLDetailsElement | null} */
const element = document.querySelector(`${url.hash} details.element-main`);
if (element == null) {
return;
}
element.open = true;
});
}
/**
* More common API, listens to "hashchange" event.
* @returns {void}
*/
function handleHashChangeEvent() {
window.addEventListener("hashchange", () => {
/** @type {HTMLDetailsElement | null} */
const element = document.querySelector(`${window.location.hash} details.element-main`);
if (element == null) {
return;
}
element.open = true;
});
}
// =================
// Animate <details>
// =================
function animateDetailsTag() {
const prioritiesContainer = document.querySelector(".priorities-container");
if (!(prioritiesContainer instanceof HTMLElement)) {
throw new Error("Couldn't find .priorities-container");
}
/**
* Triggered on opening animation end.
* @param {AnimationEvent} event
*/
const onAnimationEnd = (event) => {
let target = event.target;
while (true) {
if (!(target instanceof HTMLElement)) {
return;
}
if (target instanceof HTMLDetailsElement) {
break;
}
target = target.parentElement;
}
target.classList.remove("opening");
};
const mutationObserver = new MutationObserver((mutationList, _) => {
for (const mutation of mutationList) {
const target = mutation.target;
if (!(target instanceof HTMLDetailsElement)) {
continue;
}
if (mutation.attributeName === "open") {
target.removeEventListener("animationend", onAnimationEnd);
if (target.open) {
// Just opened.
target.classList.add("opening");
target.addEventListener("animationend", onAnimationEnd);
} else {
target.classList.remove("opening");
}
}
}
});
mutationObserver.observe(prioritiesContainer, { attributes: true, subtree: true });
}
// ====
// Main
// ====
/**
* Main function.
*/
function main() {
addBlankTargetToElementLinks();
setupScrollToTop();
openDetailsSelectedInUrl();
handleNavigateEvent();
animateDetailsTag();
}
main();