my website built with vue, plus lexicon definitions for moe.wlo.gallery.* vt3e.cat
at develop 3.0 kB view raw
1import { html } from "./html"; 2 3export interface Route { 4 path: string; 5 component: () => Promise<string> | string; 6 title?: string; 7 showInNavigation?: boolean; 8} 9 10export class Router { 11 private currentRoute: Route | null = null; 12 private loadedRoutes: Set<string> = new Set(); 13 isLoading: boolean = false; 14 routes: Route[] = []; 15 16 constructor(private container: HTMLElement) { 17 window.addEventListener("popstate", () => this.handleRoute()); 18 document.addEventListener("click", (e) => this.handleClick(e)); 19 } 20 21 private async render() { 22 if (!this.currentRoute) return; 23 24 this.isLoading = true; 25 const preloaded = this.loadedRoutes.has(this.currentRoute.path); 26 if (!preloaded) { 27 this.container.style.opacity = "0.5"; 28 this.container.style.pointerEvents = "none"; 29 } 30 31 try { 32 const result = this.currentRoute.component(); 33 const content = result instanceof Promise ? await result : result; 34 this.loadedRoutes.add(this.currentRoute.path); 35 36 if (!preloaded) await new Promise((resolve) => setTimeout(resolve, 150)); 37 this.container.innerHTML = content; 38 39 if (!preloaded) { 40 this.container.style.opacity = "1"; 41 this.container.style.pointerEvents = "auto"; 42 } 43 } catch (error) { 44 console.error("failed to load page:", error); 45 this.container.innerHTML = this.getErrorContent(); 46 this.container.style.opacity = "1"; 47 this.container.style.pointerEvents = "auto"; 48 } finally { 49 this.isLoading = false; 50 } 51 } 52 53 addRoute(route: Route) { 54 this.routes.push(route); 55 return this; 56 } 57 58 private handleClick(e: Event) { 59 const target = e.target as HTMLElement; 60 const link = target.closest("a[data-link]"); 61 62 if (link) { 63 e.preventDefault(); 64 const href = link.getAttribute("href"); 65 if (href) { 66 this.navigate(href); 67 } 68 } 69 } 70 71 private handleRoute() { 72 const path = window.location.pathname; 73 const route = 74 this.routes.find((r) => r.path === path) || 75 this.routes.find((r) => r.path === "*"); 76 77 if (route) { 78 this.currentRoute = route; 79 this.render(); 80 if (route.title) { 81 document.title = `${route.title} - willow!`; 82 } 83 this.updateNavigation(path); 84 } 85 } 86 87 updateNavigation = (currentPath: string) => { 88 const navLinks = document.querySelectorAll( 89 ".navigation-bar__nav a[data-link]", 90 ); 91 navLinks.forEach((link) => { 92 const href = link.getAttribute("href"); 93 if (href === currentPath) { 94 link.classList.add("active"); 95 } else { 96 link.classList.remove("active"); 97 } 98 }); 99 }; 100 101 navigate(path: string) { 102 window.history.pushState({}, "", path); 103 this.handleRoute(); 104 } 105 106 private getErrorContent() { 107 return html` 108 <div class="error"> 109 <h1>error</h1> 110 <p>failed to load page. please try again.</p> 111 <div class="error__actions"> 112 <a href="/" class="button__pill" data-link>go home</a> 113 <button class="button__pill reload" onclick="location.reload()"> 114 reload 115 </button> 116 </div> 117 </div> 118 `; 119 } 120 121 start() { 122 this.handleRoute(); 123 } 124}