mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-10 06:31:59 +00:00
- Implemented methods for fetching AI stats, training history, and recent experiences. - Added functionality to set operation mode (MANUAL, AUTO, AI) with appropriate handling. - Included helper methods for querying the database and sending JSON responses. - Integrated model metadata extraction for visualization purposes.
135 lines
3.2 KiB
JavaScript
135 lines
3.2 KiB
JavaScript
/**
|
|
* Hash-based SPA router.
|
|
* Routes map to lazy-loaded page modules (ES modules with mount/unmount).
|
|
*
|
|
* Each page module must export:
|
|
* mount(container, ctx): void | Promise<void>
|
|
* unmount(): void
|
|
* onRouteParams?(params): void [optional]
|
|
*
|
|
* The router guarantees unmount() is called before switching pages.
|
|
*/
|
|
|
|
import { updateDOM as updateI18n, t } from './i18n.js';
|
|
|
|
/** @type {Map<string, () => Promise<{mount, unmount, onRouteParams?}>>} */
|
|
const _routes = new Map();
|
|
let _currentModule = null;
|
|
let _currentRoute = null;
|
|
let _container = null;
|
|
let _notFoundHandler = null;
|
|
|
|
/**
|
|
* Register a route.
|
|
* @param {string} path - hash path without '#', e.g. '/dashboard'
|
|
* @param {Function} loader - async function returning the module, e.g. () => import('../pages/dashboard.js')
|
|
*/
|
|
export function route(path, loader) {
|
|
_routes.set(path, loader);
|
|
}
|
|
|
|
/**
|
|
* Set a fallback handler for unknown routes.
|
|
* @param {Function} handler - (container, hash) => void
|
|
*/
|
|
export function setNotFound(handler) {
|
|
_notFoundHandler = handler;
|
|
}
|
|
|
|
/**
|
|
* Initialize the router.
|
|
* @param {HTMLElement} container - the element to mount pages into (e.g. #app)
|
|
*/
|
|
export function init(container) {
|
|
_container = container;
|
|
window.addEventListener('hashchange', () => _resolve());
|
|
// Initial route
|
|
_resolve();
|
|
}
|
|
|
|
/**
|
|
* Force remount of the current route (used for i18n/theme refresh).
|
|
*/
|
|
export function reloadCurrent() {
|
|
_resolve(true);
|
|
}
|
|
|
|
/**
|
|
* Programmatic navigation.
|
|
* @param {string} path - e.g. '/dashboard'
|
|
*/
|
|
export function navigate(path) {
|
|
window.location.hash = '#' + path;
|
|
}
|
|
|
|
/**
|
|
* Get current route path.
|
|
*/
|
|
export function currentRoute() {
|
|
return _currentRoute;
|
|
}
|
|
|
|
/* -- Internal -- */
|
|
|
|
async function _resolve(force = false) {
|
|
const hash = window.location.hash.slice(1) || '/dashboard'; // default
|
|
const [path, queryStr] = hash.split('?');
|
|
const params = Object.fromEntries(new URLSearchParams(queryStr || ''));
|
|
|
|
// If same route, just update params
|
|
if (!force && path === _currentRoute && _currentModule?.onRouteParams) {
|
|
_currentModule.onRouteParams(params);
|
|
return;
|
|
}
|
|
|
|
// Unmount previous
|
|
if (_currentModule) {
|
|
try {
|
|
_currentModule.unmount();
|
|
} catch (err) {
|
|
console.error(`[Router] Error unmounting ${_currentRoute}:`, err);
|
|
}
|
|
_currentModule = null;
|
|
}
|
|
|
|
// Clear container
|
|
_container.innerHTML = '';
|
|
_currentRoute = path;
|
|
|
|
// Find matching route
|
|
const loader = _routes.get(path);
|
|
if (!loader) {
|
|
if (_notFoundHandler) {
|
|
_notFoundHandler(_container, path);
|
|
} else {
|
|
_container.textContent = t('router.notFound', { path });
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Loading indicator
|
|
_container.setAttribute('aria-busy', 'true');
|
|
|
|
try {
|
|
const mod = await loader();
|
|
_currentModule = mod;
|
|
|
|
// Mount the page
|
|
await mod.mount(_container, { params, navigate });
|
|
|
|
// Update i18n labels in the newly mounted content
|
|
updateI18n(_container);
|
|
|
|
// Pass route params if handler exists
|
|
if (mod.onRouteParams) {
|
|
mod.onRouteParams(params);
|
|
}
|
|
|
|
} catch (err) {
|
|
console.error(`[Router] Error loading ${path}:`, err);
|
|
_container.textContent = t('router.errorLoading', { message: err.message });
|
|
} finally {
|
|
_container.removeAttribute('aria-busy');
|
|
}
|
|
}
|