overview / js /utils /router.js
yjernite's picture
yjernite HF Staff
Upload 4 files
98f8ae8 verified
raw
history blame
15 kB
// router.js - Simple SPA router
import { renderHomePage, getHomePageSidebar } from '../pages/HomePage.js';
import { renderAreaPage, getAreaPageSidebar } from '../pages/AreaPage.js';
import { renderResourcesPage, getResourcesPageSidebar } from '../pages/ResourcesPage.js';
import { scrollToSection } from './dom.js';
class Router {
constructor() {
this.routes = {
'/': 'home',
'/home': 'home',
'/efficiency': 'efficiency',
'/personal': 'personal',
'/rights': 'rights',
'/ecosystems': 'ecosystems',
'/about': 'resources'
};
this.currentPage = null;
this.init();
}
init() {
// Handle initial page load
console.log('Initializing router');
this.loadPage(window.location.pathname);
// Handle browser back/forward
window.addEventListener('popstate', (e) => {
const path = window.location.pathname;
const hash = window.location.hash;
const fullUrl = path + hash;
// Use navigateToUrl to ensure proper scroll behavior
this.navigateToUrl(fullUrl);
});
// Handle hash changes for navigation
window.addEventListener('hashchange', (e) => {
const hash = window.location.hash.substring(1);
if (hash) {
// Scroll to the section after a short delay to ensure content is loaded
setTimeout(() => scrollToSection(hash), 100);
}
});
// Handle initial hash if present
const initialHash = window.location.hash.substring(1);
if (initialHash) {
setTimeout(() => scrollToSection(initialHash), 200);
}
// Initialize unified navigation handling
this.initializeNavigation();
}
async loadPage(path) {
const route = this.routes[path] || 'home';
if (this.currentPage === route) {
// Same page, no need to reload
return;
}
this.currentPage = route;
console.log('Loading page:', route);
// Handle background for all pages
await this.setPageBackground(route);
console.log('Background set for:', route);
try {
switch (route) {
case 'home':
await this.loadHomePage();
break;
case 'efficiency':
case 'personal':
case 'rights':
case 'ecosystems':
await this.loadAreaPage(route);
break;
case 'resources':
await this.loadResourcesPage();
break;
default:
await this.loadHomePage();
}
} catch (error) {
console.error('Error loading page:', error);
// Fallback to home page
await this.loadHomePage();
}
console.log('Page loaded:', route);
return Promise.resolve();
}
async setPageBackground(route) {
console.log('Setting background for route:', route);
// Clear any existing background
this.clearBackground();
let backgroundImage, attribution, sourceUrl;
if (route === 'home' || route === 'resources') {
const { homeBackgroundImage } = await import('../data/areas.js');
backgroundImage = homeBackgroundImage.image;
attribution = homeBackgroundImage.attribution;
sourceUrl = homeBackgroundImage.sourceUrl;
} else {
// Area pages
const { areasData } = await import('../data/areas.js');
const area = areasData[route];
backgroundImage = area.image;
attribution = area.imageAttribution;
sourceUrl = area.imageSourceUrl;
}
// Create background element at document level
const backgroundDiv = document.createElement('div');
backgroundDiv.id = 'page-background';
backgroundDiv.className = 'fixed opacity-40 z-0';
// The positioning logic is now in updatePageBackgroundPosition, call it after adding to DOM
document.body.insertAdjacentElement('afterbegin', backgroundDiv);
updatePageBackgroundPosition(); // Set initial position
backgroundDiv.innerHTML = `
<!-- Background Image Main Content -->
<img src="images/${backgroundImage}" alt="" class="w-full h-full object-cover pointer-events-none">
<div id="bg-attribution" class="absolute bottom-4 right-4 bg-black bg-opacity-75 text-white text-xs px-2 py-1 rounded opacity-0 transition-opacity duration-200 max-w-xs z-50">
<a href="${sourceUrl}" target="_blank" class="text-blue-300 hover:text-blue-100">
${attribution}
</a>
</div>
`;
// Add hover functionality for attribution
this.initializeBackgroundAttribution();
}
clearBackground() {
const existingBg = document.getElementById('page-background');
if (existingBg) {
existingBg.remove();
}
}
initializeBackgroundAttribution() {
const backgroundContainer = document.getElementById('page-background');
const attribution = document.getElementById('bg-attribution');
if (!backgroundContainer || !attribution) return;
backgroundContainer.addEventListener('mouseenter', () => {
attribution.style.opacity = '1';
});
backgroundContainer.addEventListener('mouseleave', () => {
attribution.style.opacity = '0';
});
}
async loadHomePage() {
const mainContent = document.getElementById('main-content');
const leftSidebar = document.getElementById('left-sidebar');
if (!mainContent) {
return;
}
try {
// Get page content and sidebar
const homePage = renderHomePage();
// Update content
const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent;
contentContainer.innerHTML = homePage.content;
// Update sidebar if it exists
if (leftSidebar) {
leftSidebar.innerHTML = getHomePageSidebar();
}
// Initialize page
if (homePage.init) {
homePage.init();
}
// Initialize left sidebar attribution after content is loaded
initializeLeftSidebarAttribution();
// Update navigation state
this.updateNavigation('home');
} catch (error) {
// Fallback to error content
const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent;
contentContainer.innerHTML = `
<div class="bg-white rounded-lg shadow-sm p-8">
<h1 class="text-3xl font-bold text-red-600 mb-6">Error Loading Page</h1>
<p class="text-gray-700">Sorry, there was an error loading the home page.</p>
</div>
`;
}
}
async loadAreaPage(area) {
const mainContent = document.getElementById('main-content');
const leftSidebar = document.getElementById('left-sidebar');
if (!mainContent) {
return;
}
try {
// Get area page content and sidebar
const areaPage = renderAreaPage(area);
// Update content
const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent;
contentContainer.innerHTML = areaPage.content;
// Update sidebar
if (leftSidebar) {
leftSidebar.innerHTML = getAreaPageSidebar(area);
}
// Initialize page
if (areaPage.init) {
areaPage.init();
}
// Initialize left sidebar attribution after content is loaded
initializeLeftSidebarAttribution();
} catch (error) {
console.error(`Error loading ${area} page:`, error);
// Fallback content
const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent;
contentContainer.innerHTML = `
<div class="bg-white rounded-lg shadow-sm p-8">
<h1 class="text-3xl font-bold text-red-600 mb-6">Error Loading Page</h1>
<p class="text-gray-700">Sorry, there was an error loading the ${area} page.</p>
</div>
`;
}
this.updateNavigation(area);
}
async loadResourcesPage() {
const mainContent = document.getElementById('main-content');
const leftSidebar = document.getElementById('left-sidebar');
if (!mainContent) return;
try {
// Get resources page content and sidebar
const resourcesPage = renderResourcesPage();
// Update content
const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent;
contentContainer.innerHTML = resourcesPage.content;
// Update sidebar
if (leftSidebar) {
leftSidebar.innerHTML = getResourcesPageSidebar();
}
// Initialize page
if (resourcesPage.init) {
resourcesPage.init();
}
// Initialize left sidebar attribution after content is loaded
initializeLeftSidebarAttribution();
} catch (error) {
// Fallback content
const contentContainer = mainContent.querySelector('.max-w-4xl') || mainContent;
contentContainer.innerHTML = `
<div class="bg-white rounded-lg shadow-sm p-8">
<h1 class="text-3xl font-bold text-red-600 mb-6">Error Loading Page</h1>
<p class="text-gray-700">Sorry, there was an error loading the resources page.</p>
</div>
`;
}
this.updateNavigation('resources');
}
updateNavigation(currentPage) {
// Update header navigation active states
const navLinks = document.querySelectorAll('header nav a');
navLinks.forEach(link => {
link.classList.remove('text-blue-600', 'bg-blue-50');
link.classList.add('text-gray-700');
const href = link.getAttribute('href');
if ((currentPage === 'home' && (href === '/' || href === '/home')) ||
(currentPage === 'resources' && href === '/about') ||
(currentPage !== 'home' && currentPage !== 'resources' && href === `/${currentPage}`)) {
link.classList.remove('text-gray-700');
link.classList.add('text-blue-600', 'bg-blue-50');
}
});
}
initializeNavigation() {
// Use event delegation to handle all navigation links
document.addEventListener('click', (e) => {
const link = e.target.closest('a');
if (!link) return;
const href = link.getAttribute('href');
if (!href) return;
// Handle different types of links
if (href.startsWith('/') && !href.startsWith('/js/') && !href.startsWith('/css/') && !href.startsWith('/images/')) {
// Check if this is a known SPA route
const path = href.split('#')[0];
if (this.routes[path]) {
e.preventDefault();
this.navigateToUrl(href);
}
} else if (href.startsWith('#')) {
// Same-page hash navigation
e.preventDefault();
scrollToSection(href.substring(1));
}
// External links are handled normally by browser
});
}
navigateToUrl(fullUrl) {
// Parse URL into path and hash
const [path, hash] = fullUrl.split('#');
// Update browser URL
window.history.pushState({}, '', fullUrl);
// Load page and then handle hash
this.loadPage(path).then(() => {
if (hash) {
setTimeout(() => scrollToSection(hash), 100);
} else {
// No hash - scroll to top of page with a delay to ensure content is rendered
setTimeout(() => {
window.scrollTo({ top: 0, behavior: 'smooth' });
}, 100);
}
});
}
}
// Export router instance
export const router = new Router();
// New function to update the position and size of the page background
function updatePageBackgroundPosition() {
const backgroundDiv = document.getElementById('page-background');
if (!backgroundDiv) return;
const headerHeight = getComputedStyle(document.documentElement).getPropertyValue('--header-height');
const leftSidebar = document.getElementById('left-sidebar');
const searchSidebar = document.getElementById('search-sidebar');
const leftSidebarWidth = leftSidebar.classList.contains('-translate-x-full') ? 0 : leftSidebar.offsetWidth;
const rightSidebarWidth = searchSidebar.classList.contains('translate-x-full') ? 0 : searchSidebar.offsetWidth;
backgroundDiv.style.cssText = `
top: ${headerHeight};
left: ${leftSidebarWidth}px;
right: ${rightSidebarWidth}px;
bottom: 0;
width: calc(100% - ${leftSidebarWidth + rightSidebarWidth}px);
height: calc(100vh - ${headerHeight});
transition: left 0.3s ease-in-out, width 0.3s ease-in-out, right 0.3s ease-in-out; /* Add transition for smooth movement */
`;
}
// Export the new function so main.js can call it
export { updatePageBackgroundPosition };
// Function to initialize the attribution hover for the left sidebar
function initializeLeftSidebarAttribution() {
const leftSidebarContainer = document.getElementById('left-sidebar');
const attribution = document.getElementById('left-sidebar-attribution');
if (!leftSidebarContainer || !attribution) return;
leftSidebarContainer.addEventListener('mouseenter', () => {
attribution.style.opacity = '1';
});
leftSidebarContainer.addEventListener('mouseleave', () => {
attribution.style.opacity = '0';
});
}
// Export the new function as well
export { initializeLeftSidebarAttribution };