77 lines
2.0 KiB
JavaScript
77 lines
2.0 KiB
JavaScript
const routes = [];
|
|
let currentCleanup = null;
|
|
|
|
/** @returns {{ regex: RegExp, paramNames: string[] }} */
|
|
export function compileRoute(pattern) {
|
|
const paramNames = [];
|
|
const parts = pattern.replace(/\/:([^/]+)/g, (_, name) => {
|
|
paramNames.push(name);
|
|
return '/([^/]+)';
|
|
});
|
|
return { regex: new RegExp('^' + parts + '$'), paramNames };
|
|
}
|
|
|
|
/** Match a path against a string route pattern; returns params or null. */
|
|
export function matchRoute(pattern, path) {
|
|
const { regex, paramNames } = compileRoute(pattern);
|
|
const match = path.match(regex);
|
|
if (!match) {
|
|
return null;
|
|
}
|
|
const params = {};
|
|
paramNames.forEach((name, i) => {
|
|
params[name] = decodeURIComponent(match[i + 1]);
|
|
});
|
|
return params;
|
|
}
|
|
|
|
export function addRoute(pattern, handler) {
|
|
let regex;
|
|
let paramNames = [];
|
|
if (pattern instanceof RegExp) {
|
|
regex = pattern;
|
|
} else {
|
|
({ regex, paramNames } = compileRoute(pattern));
|
|
}
|
|
routes.push({ regex, paramNames, handler });
|
|
}
|
|
|
|
export function navigate(hash) {
|
|
window.location.hash = hash;
|
|
}
|
|
|
|
export function currentHash() {
|
|
return window.location.hash.slice(1) || '/';
|
|
}
|
|
|
|
async function resolve() {
|
|
if (typeof currentCleanup === 'function') {
|
|
currentCleanup();
|
|
currentCleanup = null;
|
|
}
|
|
|
|
const path = currentHash();
|
|
for (const route of routes) {
|
|
const match = path.match(route.regex);
|
|
if (match) {
|
|
const params = {};
|
|
route.paramNames.forEach((name, i) => {
|
|
params[name] = decodeURIComponent(match[i + 1]);
|
|
});
|
|
try {
|
|
currentCleanup = await route.handler(params) || null;
|
|
} catch (e) {
|
|
console.error('Route handler error:', e);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
// fallback: dashboard
|
|
navigate('#/');
|
|
}
|
|
|
|
export function startRouter() {
|
|
window.addEventListener('hashchange', resolve);
|
|
resolve();
|
|
}
|