|
|
|
|
|
import { renderHomePage } from '../pages/HomePage.js'; |
|
|
import { renderAreaPage } from '../pages/AreaPage.js'; |
|
|
import { renderResourcesPage } from '../pages/ResourcesPage.js'; |
|
|
import { scrollToSection } from '../main.js'; |
|
|
|
|
|
class Router { |
|
|
constructor() { |
|
|
|
|
|
this.routes = { |
|
|
'/': 'home', |
|
|
'/home': 'home', |
|
|
'/about': 'resources' |
|
|
}; |
|
|
|
|
|
|
|
|
const areasData = window.areasData || {}; |
|
|
Object.keys(areasData).forEach(areaId => { |
|
|
this.routes[`/${areaId}`] = areaId; |
|
|
}); |
|
|
|
|
|
|
|
|
this.areas = Object.keys(areasData); |
|
|
|
|
|
this.currentPage = null; |
|
|
this.currentArea = null; |
|
|
this.currentTopic = null; |
|
|
this.init(); |
|
|
} |
|
|
|
|
|
init() { |
|
|
|
|
|
this.loadPage(window.location.pathname); |
|
|
|
|
|
|
|
|
window.addEventListener('popstate', (e) => { |
|
|
const path = window.location.pathname; |
|
|
const hash = window.location.hash; |
|
|
const fullUrl = path + hash; |
|
|
|
|
|
|
|
|
this.navigateToUrl(fullUrl, false); |
|
|
}); |
|
|
|
|
|
|
|
|
window.addEventListener('hashchange', (e) => { |
|
|
const hash = window.location.hash.substring(1); |
|
|
if (hash) { |
|
|
|
|
|
setTimeout(() => scrollToSection(hash), 100); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
const initialHash = window.location.hash.substring(1); |
|
|
if (initialHash) { |
|
|
setTimeout(() => scrollToSection(initialHash), 200); |
|
|
} |
|
|
|
|
|
|
|
|
this.initializeNavigation(); |
|
|
} |
|
|
|
|
|
async loadPage(path) { |
|
|
|
|
|
const pathParts = path.split('/').filter(p => p); |
|
|
let route, area, topic; |
|
|
|
|
|
if (pathParts.length === 0) { |
|
|
|
|
|
route = 'home'; |
|
|
} else if (pathParts.length === 1) { |
|
|
|
|
|
route = this.routes[path] || 'home'; |
|
|
if (this.areas.includes(pathParts[0])) { |
|
|
area = pathParts[0]; |
|
|
} |
|
|
} else if (pathParts.length === 2 && this.areas.includes(pathParts[0])) { |
|
|
|
|
|
area = pathParts[0]; |
|
|
topic = pathParts[1]; |
|
|
route = area; |
|
|
} else { |
|
|
|
|
|
route = 'home'; |
|
|
} |
|
|
|
|
|
|
|
|
this.currentPage = route; |
|
|
this.currentArea = area || null; |
|
|
this.currentTopic = topic || null; |
|
|
|
|
|
|
|
|
if (window.Alpine && window.Alpine.store) { |
|
|
const navStore = window.Alpine.store('navigation'); |
|
|
if (navStore) { |
|
|
navStore.currentPage = this.currentPage; |
|
|
navStore.currentArea = this.currentArea; |
|
|
navStore.currentTopic = this.currentTopic; |
|
|
navStore.updateTopicNav(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
window.dispatchEvent(new CustomEvent('navigation-changed', { |
|
|
detail: { |
|
|
currentPage: this.currentPage, |
|
|
currentArea: this.currentArea, |
|
|
currentTopic: this.currentTopic |
|
|
} |
|
|
})); |
|
|
|
|
|
|
|
|
await this.setPageBackground(route); |
|
|
|
|
|
try { |
|
|
|
|
|
if (route === 'home') { |
|
|
await this.loadHomePage(); |
|
|
} else if (route === 'resources') { |
|
|
await this.loadResourcesPage(); |
|
|
} else if (this.areas.includes(route)) { |
|
|
|
|
|
await this.loadAreaPage(route, topic); |
|
|
} else { |
|
|
|
|
|
await this.loadHomePage(); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Error loading page:', error); |
|
|
|
|
|
await this.loadHomePage(); |
|
|
} |
|
|
|
|
|
return Promise.resolve(); |
|
|
} |
|
|
|
|
|
async setPageBackground(route) { |
|
|
|
|
|
this.clearBackground(); |
|
|
|
|
|
let backgroundImage, attribution, sourceUrl; |
|
|
|
|
|
|
|
|
if (route === 'home' || route === 'resources') { |
|
|
const homeBackgroundImage = window.homeBackgroundImage; |
|
|
backgroundImage = homeBackgroundImage.image; |
|
|
attribution = homeBackgroundImage.attribution; |
|
|
sourceUrl = homeBackgroundImage.sourceUrl; |
|
|
} else { |
|
|
|
|
|
const areasData = window.areasData; |
|
|
const area = areasData[route]; |
|
|
backgroundImage = area.image; |
|
|
attribution = area.imageAttribution; |
|
|
sourceUrl = area.imageSourceUrl; |
|
|
} |
|
|
|
|
|
|
|
|
const backgroundDiv = document.createElement('div'); |
|
|
backgroundDiv.id = 'page-background'; |
|
|
backgroundDiv.className = 'fixed opacity-40 z-0'; |
|
|
|
|
|
|
|
|
document.body.insertAdjacentElement('afterbegin', backgroundDiv); |
|
|
updatePageBackgroundPosition(); |
|
|
|
|
|
backgroundDiv.innerHTML = ` |
|
|
<!-- Background Image Main Content --> |
|
|
<img src="/images/${backgroundImage}" alt="" class="w-full h-full 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> |
|
|
`; |
|
|
|
|
|
|
|
|
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'); |
|
|
|
|
|
if (!mainContent) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
const homePage = renderHomePage(); |
|
|
|
|
|
|
|
|
mainContent.innerHTML = homePage.content; |
|
|
|
|
|
|
|
|
if (homePage.init) { |
|
|
homePage.init(); |
|
|
} |
|
|
|
|
|
|
|
|
this.updateNavigation('home'); |
|
|
} catch (error) { |
|
|
console.error('Error loading home page:', error); |
|
|
console.error('Error stack:', error.stack); |
|
|
console.error('Error message:', error.message); |
|
|
|
|
|
|
|
|
mainContent.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 mb-4">Sorry, there was an error loading the home page.</p> |
|
|
<pre class="text-sm bg-gray-100 p-4 rounded overflow-auto">${error.message}\n\n${error.stack}</pre> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
} |
|
|
|
|
|
async loadAreaPage(area, topic = null) { |
|
|
const mainContent = document.getElementById('main-content'); |
|
|
|
|
|
if (!mainContent) { |
|
|
return; |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
const areaPage = renderAreaPage(area, topic); |
|
|
|
|
|
|
|
|
mainContent.innerHTML = areaPage.content; |
|
|
|
|
|
|
|
|
if (areaPage.init) { |
|
|
areaPage.init(); |
|
|
} |
|
|
|
|
|
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' }); |
|
|
|
|
|
} catch (error) { |
|
|
console.error(`Error loading ${area} page:`, error); |
|
|
|
|
|
|
|
|
mainContent.innerHTML = ` |
|
|
<div class="bg-white rounded-lg shadow-sm p-8 mx-4"> |
|
|
<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'); |
|
|
|
|
|
if (!mainContent) return; |
|
|
|
|
|
try { |
|
|
|
|
|
const resourcesPage = renderResourcesPage(); |
|
|
|
|
|
|
|
|
mainContent.innerHTML = resourcesPage.content; |
|
|
|
|
|
|
|
|
if (resourcesPage.init) { |
|
|
resourcesPage.init(); |
|
|
} |
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
mainContent.innerHTML = ` |
|
|
<div class="bg-white rounded-lg shadow-sm p-8 mx-4"> |
|
|
<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) { |
|
|
|
|
|
const navLinks = document.querySelectorAll('header nav a'); |
|
|
navLinks.forEach(link => { |
|
|
|
|
|
link.classList.remove('text-blue-600', 'bg-blue-50', 'font-semibold'); |
|
|
|
|
|
const href = link.getAttribute('href'); |
|
|
|
|
|
if ((currentPage === 'home' && (href === '/' || href === '/home')) || |
|
|
(currentPage === 'resources' && href === '/about') || |
|
|
(currentPage !== 'home' && currentPage !== 'resources' && href === `/${currentPage}`)) { |
|
|
link.classList.add('text-blue-600', 'bg-blue-50', 'font-semibold'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
initializeNavigation() { |
|
|
|
|
|
document.addEventListener('click', (e) => { |
|
|
const link = e.target.closest('a'); |
|
|
if (!link) return; |
|
|
|
|
|
const href = link.getAttribute('href'); |
|
|
if (!href) return; |
|
|
|
|
|
|
|
|
if (href.startsWith('/') && !href.startsWith('/js/') && !href.startsWith('/css/') && !href.startsWith('/images/')) { |
|
|
|
|
|
const path = href.split('#')[0]; |
|
|
const pathParts = path.split('/').filter(p => p); |
|
|
const isKnownRoute = this.routes[path] || |
|
|
(pathParts.length === 2 && this.areas.includes(pathParts[0])); |
|
|
|
|
|
if (isKnownRoute) { |
|
|
e.preventDefault(); |
|
|
this.navigateToUrl(href); |
|
|
} |
|
|
} else if (href.startsWith('#')) { |
|
|
|
|
|
e.preventDefault(); |
|
|
scrollToSection(href.substring(1)); |
|
|
} |
|
|
|
|
|
}); |
|
|
} |
|
|
|
|
|
navigateToUrl(fullUrl, pushState = true) { |
|
|
|
|
|
const [path, hash] = fullUrl.split('#'); |
|
|
|
|
|
|
|
|
if (pushState) { |
|
|
window.history.pushState({}, '', fullUrl); |
|
|
} |
|
|
|
|
|
|
|
|
this.loadPage(path).then(() => { |
|
|
if (hash) { |
|
|
setTimeout(() => scrollToSection(hash), 100); |
|
|
} else { |
|
|
|
|
|
setTimeout(() => { |
|
|
window.scrollTo({ top: 0, behavior: 'smooth' }); |
|
|
}, 100); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
export const router = new Router(); |
|
|
|
|
|
|
|
|
window.router = router; |
|
|
|
|
|
|
|
|
function updatePageBackgroundPosition() { |
|
|
const backgroundDiv = document.getElementById('page-background'); |
|
|
if (!backgroundDiv) return; |
|
|
|
|
|
const headerHeight = getComputedStyle(document.documentElement).getPropertyValue('--header-height'); |
|
|
const searchSidebar = document.getElementById('search-sidebar'); |
|
|
|
|
|
const rightSidebarWidth = searchSidebar && !searchSidebar.classList.contains('translate-x-full') ? searchSidebar.offsetWidth : 0; |
|
|
|
|
|
backgroundDiv.style.cssText = ` |
|
|
top: ${headerHeight}; |
|
|
left: 0; |
|
|
right: ${rightSidebarWidth}px; |
|
|
bottom: 0; |
|
|
width: calc(100% - ${rightSidebarWidth}px); |
|
|
height: calc(100vh - ${headerHeight}); |
|
|
transition: width 0.3s ease-in-out, right 0.3s ease-in-out; |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
export { updatePageBackgroundPosition }; |
|
|
|