// Configuration Router - Loads appropriate config component based on URL
class ConfigRouter {
constructor() {
this.currentComponent = null;
}
async init() {
//console.log('ConfigRouter: Initializing...');
// Get current category from query parameter or global variable
const searchParams = new URLSearchParams(window.location.search);
let currentCategory = searchParams.get('config') || window.configCategory || 'general';
//console.log(`Initial parsing - searchParams.get('config'): ${searchParams.get('config')}`);
//console.log(`Window location search: ${window.location.search}`);
//console.log(`Initial currentCategory: ${currentCategory}`);
// Handle the case where URL is config?=backup (malformed but common)
// Check if we got the category from URL params, not from fallback
const gotFromUrlParams = searchParams.get('config') !== null;
if (!gotFromUrlParams && window.location.search.includes('?=')) {
//console.log(`URL contains ?= and no valid config param, attempting regex match...`);
const pathMatch = window.location.search.match(/\?=([^&]+)/);
//console.log(`Regex match result:`, pathMatch);
//console.log(`Regex test result:`, /\?=([^&]+)/.test(window.location.search));
if (pathMatch && pathMatch[1]) {
currentCategory = pathMatch[1];
//console.log(`Updated currentCategory from regex: ${currentCategory}`);
} else {
//console.log(`Regex failed to match, currentCategory remains: ${currentCategory}`);
}
} else {
//console.log(`Got category from URL params (${gotFromUrlParams}) or URL doesn't contain ?=, keeping: ${currentCategory}`);
}
//console.log(`Config router init: final category=${currentCategory}`);
// Backup config moved to /backup — redirect old URL/bookmarks.
if (currentCategory === 'backup') {
if (window.librePortalSPA && typeof window.librePortalSPA.navigate === 'function') {
window.librePortalSPA.navigate('/backup', true);
} else {
window.location.href = '/backup';
}
return;
}
// Load categories for sidebar
await this.loadConfigCategories();
// Set active category in sidebar
this.setActiveCategory(currentCategory);
// Load appropriate config component
await this.loadConfigComponent(currentCategory);
}
async loadConfigCategories() {
try {
//console.log('Loading config categories from: data/config/generated/configs.json');
// Start loading bar
if (typeof router !== 'undefined' && router.updateProgress) {
router.updateProgress(20);
}
// Load unified config file
const response = await fetch('/data/config/generated/configs.json');
//console.log('Response status:', response.status, response.statusText);
//console.log('Response ok:', response.ok);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const text = await response.text();
//console.log('Raw response text:', text);
//console.log('Text length:', text.length);
//console.log('First 100 chars:', text.substring(0, 100));
if (!text || text.trim() === '') {
throw new Error('Empty response from configs.json');
}
const data = JSON.parse(text);
const categories = data.categories;
//console.log('Parsed categories:', categories);
if (typeof router !== 'undefined' && router.updateProgress) {
router.updateProgress(60);
}
const categoriesList = document.getElementById('config-categories-list');
if (!categoriesList) {
console.error('config-categories-list element not found');
return;
}
categoriesList.innerHTML = '';
// Convert categories object to array and sort by ORDER
const categoriesArray = Object.entries(categories).map(([key, value]) => ({
id: key,
...value
}));
// Sort by ORDER if available, otherwise by title
categoriesArray.sort((a, b) => {
const orderA = parseInt(a.order) || 999;
const orderB = parseInt(b.order) || 999;
return orderA - orderB;
});
categoriesArray.forEach(category => {
const categoryItem = document.createElement('div');
categoryItem.className = 'category';
categoryItem.setAttribute('data-category', category.id);
// Use correct icon from our new structure
const iconName = category.icon || category.id;
const iconPath = `/icons/config/${iconName}.svg`;
//console.log(`Category: ${category.id}, Icon path: ${iconPath}`);
categoryItem.innerHTML = `
${category.title}
`;
categoryItem.addEventListener('click', () => {
//console.log(`Category clicked: ${category.id}`);
this.navigateToCategory(category.id);
});
categoriesList.appendChild(categoryItem);
});
if (typeof router !== 'undefined' && router.updateProgress) {
router.updateProgress(80);
}
} catch (error) {
console.error('Error loading config categories:', error);
if (typeof router !== 'undefined' && router.hideLoadingBar) {
router.hideLoadingBar();
}
}
}
setActiveCategory(categoryId) {
// Update active state
document.querySelectorAll('.category').forEach(item => {
item.classList.remove('active');
});
document.querySelector(`[data-category="${categoryId}"]`)?.classList.add('active');
}
navigateToCategory(categoryId) {
//console.log(`Config router: navigating to ${categoryId} (SPA mode)`);
// Update URL without full page reload using query parameter
const url = `/config?=${categoryId}`;
//console.log(`Updating URL to: ${url}`);
window.history.pushState({}, '', url);
// Set active category
this.setActiveCategory(categoryId);
// Load config content dynamically
this.loadConfigComponent(categoryId);
}
async loadConfigComponent(categoryId) {
try {
//console.log(`Config router: Loading component for ${categoryId}`);
// Start loading bar
if (typeof router !== 'undefined' && router.updateProgress) {
router.updateProgress(10);
router.showLoadingBar();
}
// Clear current content
const configSection = document.getElementById('config-section');
if (configSection) {
configSection.innerHTML = '