adobe-to-docusign-migrator/web/static/js/router.js

103 lines
2.8 KiB
JavaScript

// Hash-based client-side router
// Usage: navigate('#/templates') or window.location.hash = '#/templates'
import { escHtml } from './utils.js';
const _routes = {};
let _current = null;
// Register a route: router.register('#/templates', loadFn)
export function register(hash, loadFn) {
_routes[hash] = loadFn;
}
// Navigate to a hash route
export function navigate(hash) {
window.location.hash = hash;
}
// Navigate and pass data to the view (stored temporarily)
let _routeData = null;
export function navigateWith(hash, data) {
_routeData = data;
navigate(hash);
}
export function getRouteData() {
const d = _routeData;
_routeData = null;
return d;
}
// Parse route: '#/templates/abc123' → { base: '#/templates', param: 'abc123' }
function parseHash(hash) {
const clean = hash || '#/templates';
const parts = clean.split('/');
if (parts.length >= 3) {
return { base: parts.slice(0, 3).join('/'), param: parts[3] || null };
}
return { base: clean, param: null };
}
// Route to the current hash
async function route() {
const { base, param } = parseHash(window.location.hash);
const key = param ? base : (window.location.hash || '#/templates');
const baseKey = base;
const loader = _routes[key] || _routes[baseKey] || _routes['#/templates'];
if (!loader) return;
_current = key;
updateActiveNav(baseKey);
const outlet = document.getElementById('router-outlet');
if (outlet) outlet.classList.remove('view-enter');
try {
await loader(param);
} catch (err) {
console.error('Router error:', err);
const outlet = document.getElementById('router-outlet');
if (outlet) outlet.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">⚠️</div>
<div class="empty-state-title">Failed to load view</div>
<div class="empty-state-sub">${escHtml(err.message)}</div>
</div>`;
}
if (outlet) {
outlet.classList.add('view-enter');
// Remove class after animation to allow re-trigger
setTimeout(() => outlet.classList.remove('view-enter'), 200);
}
}
// Highlight active nav item
function updateActiveNav(hash) {
document.querySelectorAll('.nav-item').forEach(el => {
el.classList.toggle('active', el.dataset.route === hash);
});
// Update breadcrumb
const label = document.querySelector(`.nav-item[data-route="${hash}"] .nav-label`);
const breadcrumbCurrent = document.getElementById('breadcrumb-current');
if (breadcrumbCurrent && label) {
breadcrumbCurrent.textContent = label.textContent.trim();
}
}
// Init: listen for hash changes and route on load
export function init() {
window.addEventListener('hashchange', route);
// Route immediately
if (!window.location.hash || window.location.hash === '#') {
window.location.hash = '#/templates';
} else {
route();
}
}
export function current() { return _current; }