world-game/src/router/index.js

167 lines
4.7 KiB
JavaScript
Raw Normal View History

import { slides } from "@state/slides.svelte";
import { site } from "@state/site.svelte";
import { locale } from "@state/locale.svelte";
let siteInitialized = false;
function normalizePath(path) {
const stripped = path.replace(/^\/en(\/|$)/, "$1") || "/";
return stripped === "/" ? "/home" : stripped;
}
function apiPrefix() {
return locale.current === "en" ? "/en" : "";
}
/**
* Trouve l'index de la slide correspondant au path.
* Si le path exact n'existe pas, essaie le chemin parent
* (ex: /blog/article-slug /blog).
*/
function findSlideIndex(path) {
let idx = slides.getIndexByPath(path);
if (idx !== -1) return idx;
const parentPath = path.replace(/\/[^/]+$/, "");
if (parentPath) return slides.getIndexByPath(parentPath);
return -1;
}
async function loadSlide(path) {
let slidePath = path;
let idx = slides.getIndexByPath(slidePath);
// Sub-page: resolve to parent slide (ex: /blog/slug → /blog)
if (idx === -1) {
const parentPath = path.replace(/\/[^/]+$/, "");
if (parentPath && parentPath !== path) {
const parentIdx = slides.getIndexByPath(parentPath);
if (parentIdx !== -1) {
idx = parentIdx;
slidePath = parentPath;
} else if (!siteInitialized) {
// Slides not yet initialized — assume sub-page, fetch parent to bootstrap
slidePath = parentPath;
}
}
}
if (idx !== -1) {
const slide = slides.all[idx];
if (slide.loaded || slide.loading) return;
slides.setLoading(slidePath, true);
}
try {
const response = await fetch(`${apiPrefix()}${slidePath}.json`);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
console.log("slide data", data);
if (!siteInitialized && data.site) {
site.set(data.site);
locale.initialize(data.site.language, data.site.languages);
slides.init(data.site.navigation);
siteInitialized = true;
}
slides.setData(slidePath, data);
} catch (error) {
console.error(`[router] Failed to load slide ${slidePath}:`, error);
slides.setLoading(slidePath, false);
}
}
function loadAllSlidesInBackground(exceptPath) {
slides.all
.filter((s) => s.path !== exceptPath)
.forEach((s) => loadSlide(s.path));
}
export function slideTo(path, { skipHistory = false } = {}) {
path = normalizePath(path);
if (!skipHistory) {
const historyPath =
locale.current === "en"
? path === "/home"
? "/en"
: `/en${path}`
: path === "/home"
? "/"
: path;
history.pushState({}, "", historyPath);
}
const idx = findSlideIndex(path);
const slidePath = idx !== -1 ? slides.all[idx].path : path;
if (idx !== -1 && slides.all[idx].title) {
2026-03-18 16:20:47 +01:00
document.title = `World Game - ${slides.all[idx].title}`;
}
// Si on navigue vers la slide déjà active (ex: clic sur "Blog" depuis un article),
// déclencher popstate pour que la vue puisse réagir au changement d'URL.
if (idx === slides.activeIndex && !skipHistory) {
window.dispatchEvent(new PopStateEvent('popstate'));
return;
}
slides.slideTo(slidePath);
if (idx !== -1 && !slides.all[idx].loaded) {
loadSlide(slidePath);
}
}
export async function initRouter() {
// Language detection: URL prefix > localStorage > navigator
const hasEnPrefix = window.location.pathname.startsWith("/en");
if (hasEnPrefix) {
locale.setLanguage("en");
localStorage.setItem("wg_lang", "en");
} else if (!localStorage.getItem("wg_lang")) {
const navLang = navigator.language || navigator.languages?.[0] || "fr";
if (navLang.startsWith("en")) {
window.location.replace("/en" + window.location.pathname);
return;
}
} else if (localStorage.getItem("wg_lang") === "en") {
window.location.replace("/en" + window.location.pathname);
return;
}
const initialPath = normalizePath(window.location.pathname);
await loadSlide(initialPath);
const idx = findSlideIndex(initialPath);
if (idx !== -1) {
slides.setActiveIndex(idx);
}
loadAllSlidesInBackground(idx !== -1 ? slides.all[idx].path : initialPath);
window.addEventListener("popstate", () => {
const path = normalizePath(window.location.pathname);
slideTo(path, { skipHistory: true });
});
document.addEventListener("click", (e) => {
const link = e.target.closest("a");
if (!link) return;
const url = new URL(link.href, window.location.origin);
if (
url.origin === window.location.origin &&
!link.target &&
!link.hasAttribute("download")
) {
e.preventDefault();
slideTo(url.pathname);
}
});
}
// Keep navigateTo as alias so existing views don't break
export const navigateTo = slideTo;