Compare commits
No commits in common. "7a7ef3ebc15e0b847b7e9698e84fdcd9ff20e543" and "aa776f2eb03a3940ee4fb99aa80cf213e2a506d2" have entirely different histories.
7a7ef3ebc1
...
aa776f2eb0
@ -1 +0,0 @@
|
|||||||
{"sessionId":"9cea077c-56da-4223-a765-be38c688106b","pid":1384,"procStart":"1332","acquiredAt":1780168110981}
|
|
||||||
@ -149,6 +149,11 @@ class SystemLoader {
|
|||||||
window.taskEventBus.start();
|
window.taskEventBus.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize task global functions
|
||||||
|
if (typeof setupTaskGlobalFunctions === 'function') {
|
||||||
|
setupTaskGlobalFunctions();
|
||||||
|
}
|
||||||
|
|
||||||
// Create TasksManager instance
|
// Create TasksManager instance
|
||||||
if (typeof TasksManager !== 'undefined') {
|
if (typeof TasksManager !== 'undefined') {
|
||||||
window.tasksManager = new TasksManager();
|
window.tasksManager = new TasksManager();
|
||||||
@ -166,6 +171,7 @@ class SystemLoader {
|
|||||||
'/core/lib/task/task-commands.js',
|
'/core/lib/task/task-commands.js',
|
||||||
'/core/lib/task/task-actions.js',
|
'/core/lib/task/task-actions.js',
|
||||||
'/core/lib/task/task-router.js',
|
'/core/lib/task/task-router.js',
|
||||||
|
'/core/lib/task/task-global-functions.js',
|
||||||
'/core/lib/task/task-manager.js',
|
'/core/lib/task/task-manager.js',
|
||||||
'/core/lib/task/task-parameter-preserve.js',
|
'/core/lib/task/task-parameter-preserve.js',
|
||||||
'/components/tasks/js/tasks-manager.js', // base: class + constructor + init + bus wiring
|
'/components/tasks/js/tasks-manager.js', // base: class + constructor + init + bus wiring
|
||||||
|
|||||||
@ -243,6 +243,7 @@ class SystemOrchestrator {
|
|||||||
|
|
||||||
// Now initialize the SPA after all components are ready
|
// Now initialize the SPA after all components are ready
|
||||||
this.initializeOriginalSPA();
|
this.initializeOriginalSPA();
|
||||||
|
this._wireLogout();
|
||||||
|
|
||||||
this.proceedToApplication();
|
this.proceedToApplication();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -290,9 +290,11 @@ class LibrePortalSPAClean {
|
|||||||
this.showError('Page not found');
|
this.showError('Page not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update navigation highlighting after content loads. The live topbar
|
// Update navigation highlighting after content loads
|
||||||
// singleton recomputes the active nav from the current path.
|
if (typeof window.topbarNavigationHighlighting === 'function') {
|
||||||
window.topbar?.setActiveNav?.();
|
//console.log('🔗 SPA: Updating navigation highlighting after navigation');
|
||||||
|
window.topbarNavigationHighlighting();
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Navigation error:', error);
|
console.error('❌ Navigation error:', error);
|
||||||
|
|||||||
28
containers/libreportal/frontend/core/lib/task/task-global-functions.js
Executable file
28
containers/libreportal/frontend/core/lib/task/task-global-functions.js
Executable file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Global Functions for Individual Task Actions
|
||||||
|
* Extends the tasks manager with global action functions for individual tasks
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Task global functions initialization is now handled by SystemLoader
|
||||||
|
// Global task functions will be set up centrally when TasksManager is available
|
||||||
|
|
||||||
|
function setupTaskGlobalFunctions() {
|
||||||
|
if (window.TasksManager || window.tasksManager) {
|
||||||
|
// Use whichever instance is available
|
||||||
|
const tasksManager = window.tasksManager || window.TasksManager;
|
||||||
|
|
||||||
|
// Add modular action functions to global scope
|
||||||
|
window.installApp = (appName, config = '') => tasksManager.router.routeAction('install', { appName, config });
|
||||||
|
window.uninstallApp = (appName) => tasksManager.router.routeAction('uninstall', { appName });
|
||||||
|
window.restartApp = (appName) => tasksManager.router.routeAction('restart', { appName });
|
||||||
|
window.startApp = (appName) => tasksManager.router.routeAction('start', { appName });
|
||||||
|
window.stopApp = (appName) => tasksManager.router.routeAction('stop', { appName });
|
||||||
|
window.backupApp = (appName) => tasksManager.router.routeAction('backup', { appName });
|
||||||
|
window.updateConfig = (appName) => tasksManager.router.routeAction('update_config', { appName });
|
||||||
|
window.systemUpdate = () => tasksManager.router.routeAction('system_update');
|
||||||
|
|
||||||
|
//console.log('✅ Task action functions registered globally');
|
||||||
|
} else {
|
||||||
|
console.warn('⚠️ TasksManager not found, action functions not registered');
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
// DOM Helper Functions - Shared across all pages
|
// DOM Helper Functions - Shared across all pages
|
||||||
|
|
||||||
|
// DOM helpers
|
||||||
function safeGetElement(id) {
|
function safeGetElement(id) {
|
||||||
const element = document.getElementById(id);
|
const element = document.getElementById(id);
|
||||||
if (!element) {
|
if (!element) {
|
||||||
@ -23,3 +24,42 @@ function safeQuerySelectorAll(selector) {
|
|||||||
}
|
}
|
||||||
return elements;
|
return elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Navigation helpers
|
||||||
|
function setupActiveNavigation() {
|
||||||
|
const navItems = document.querySelectorAll('.nav-item');
|
||||||
|
navItems.forEach(item => {
|
||||||
|
if (item.textContent.includes('App Center')) {
|
||||||
|
item.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for element to appear
|
||||||
|
function waitForElement(id, timeout = 5000) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
resolve(element);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
observer.disconnect();
|
||||||
|
resolve(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
observer.disconnect();
|
||||||
|
reject(new Error(`Element ${id} not found within ${timeout}ms`));
|
||||||
|
}, timeout);
|
||||||
|
});
|
||||||
|
}
|
||||||
287
containers/libreportal/frontend/core/lib/util/router.js
Executable file
287
containers/libreportal/frontend/core/lib/util/router.js
Executable file
@ -0,0 +1,287 @@
|
|||||||
|
// SPA Router for LibrePortal - Handles navigation without page reloads
|
||||||
|
class Router {
|
||||||
|
constructor() {
|
||||||
|
this.routes = new Map();
|
||||||
|
this.currentRoute = null;
|
||||||
|
this.loadingBar = null;
|
||||||
|
this.contentContainer = null;
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
|
||||||
|
init() {
|
||||||
|
// Create loading bar
|
||||||
|
this.createLoadingBar();
|
||||||
|
|
||||||
|
// Get content container
|
||||||
|
this.contentContainer = document.getElementById('main-content') || document.querySelector('.main');
|
||||||
|
//console.log('Router: Content container found:', !!this.contentContainer, this.contentContainer);
|
||||||
|
|
||||||
|
// Handle browser back/forward
|
||||||
|
window.addEventListener('popstate', (e) => {
|
||||||
|
if (e.state && e.state.route) {
|
||||||
|
this.navigate(e.state.route, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Don't handle initial route here - let SPA handle it after routes are registered
|
||||||
|
//console.log('Router: Initialized, waiting for SPA to handle initial navigation');
|
||||||
|
}
|
||||||
|
|
||||||
|
createLoadingBar() {
|
||||||
|
// Create neon loading bar
|
||||||
|
this.loadingBar = document.createElement('div');
|
||||||
|
this.loadingBar.className = 'neon-loading-bar';
|
||||||
|
this.loadingBar.innerHTML = `
|
||||||
|
<div class="neon-loading-bar-inner">
|
||||||
|
<div class="neon-loading-bar-progress"></div>
|
||||||
|
<div class="neon-loading-bar-glow"></div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Insert after header
|
||||||
|
const header = document.getElementById('topbar-container');
|
||||||
|
if (header) {
|
||||||
|
header.insertAdjacentElement('afterend', this.loadingBar);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add CSS
|
||||||
|
this.addLoadingBarStyles();
|
||||||
|
}
|
||||||
|
|
||||||
|
addLoadingBarStyles() {
|
||||||
|
const style = document.createElement('style');
|
||||||
|
style.textContent = `
|
||||||
|
.neon-loading-bar {
|
||||||
|
position: fixed;
|
||||||
|
top: 60px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 3px;
|
||||||
|
background: rgba(0, 0, 0, 0.3);
|
||||||
|
z-index: 1000;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neon-loading-bar.active {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neon-loading-bar-inner {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neon-loading-bar-progress {
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
background: linear-gradient(90deg, #00d4ff, #0099ff, #00d4ff);
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
box-shadow: 0 0 10px #00d4ff;
|
||||||
|
animation: neonPulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neon-loading-bar-glow {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, #00d4ff, transparent);
|
||||||
|
opacity: 0.6;
|
||||||
|
animation: neonGlow 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes neonPulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.7; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes neonGlow {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 0.3;
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.8;
|
||||||
|
transform: scaleX(1.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.neon-loading-bar.complete .neon-loading-bar-progress {
|
||||||
|
width: 100%;
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.neon-loading-bar.complete .neon-loading-bar-glow {
|
||||||
|
animation: none;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
document.head.appendChild(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register a route
|
||||||
|
register(path, handler) {
|
||||||
|
this.routes.set(path, handler);
|
||||||
|
//console.log('Router: Registered route:', path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to a route
|
||||||
|
async navigate(path, addToHistory = true) {
|
||||||
|
if (this.currentRoute === path) return;
|
||||||
|
|
||||||
|
// Show loading bar
|
||||||
|
this.showLoadingBar();
|
||||||
|
|
||||||
|
// Update browser history
|
||||||
|
if (addToHistory) {
|
||||||
|
history.pushState({ route: path }, '', path);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.currentRoute = path;
|
||||||
|
|
||||||
|
try {
|
||||||
|
//console.log('Router: Navigating to:', path);
|
||||||
|
//console.log('Router: Available routes:', Array.from(this.routes.keys()));
|
||||||
|
|
||||||
|
// Find matching route - simple wildcard matching
|
||||||
|
let handler = null;
|
||||||
|
|
||||||
|
if (this.routes.has(path)) {
|
||||||
|
// Exact match
|
||||||
|
handler = this.routes.get(path);
|
||||||
|
} else {
|
||||||
|
// Wildcard matching
|
||||||
|
for (const [route, routeHandler] of this.routes) {
|
||||||
|
if (route.includes('*')) {
|
||||||
|
const routePattern = route.replace('*', '');
|
||||||
|
if (path.startsWith(routePattern)) {
|
||||||
|
handler = routeHandler;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
//console.log('Router: Found handler for:', path);
|
||||||
|
await handler();
|
||||||
|
} else {
|
||||||
|
console.warn('Router: No handler found for:', path);
|
||||||
|
console.warn('Router: Available routes were:', Array.from(this.routes.keys()));
|
||||||
|
this.hideLoadingBar();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Navigation error:', error);
|
||||||
|
this.hideLoadingBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoadingBar() {
|
||||||
|
if (this.loadingBar) {
|
||||||
|
this.loadingBar.classList.add('active');
|
||||||
|
// Reset progress
|
||||||
|
const progress = this.loadingBar.querySelector('.neon-loading-bar-progress');
|
||||||
|
if (progress) {
|
||||||
|
progress.style.width = '0%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress(percent) {
|
||||||
|
if (this.loadingBar) {
|
||||||
|
const progress = this.loadingBar.querySelector('.neon-loading-bar-progress');
|
||||||
|
if (progress) {
|
||||||
|
progress.style.width = `${percent}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoadingBar() {
|
||||||
|
if (this.loadingBar) {
|
||||||
|
// Complete animation
|
||||||
|
this.updateProgress(100);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadingBar.classList.add('complete');
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadingBar.classList.remove('active', 'complete');
|
||||||
|
const progress = this.loadingBar.querySelector('.neon-loading-bar-progress');
|
||||||
|
if (progress) {
|
||||||
|
progress.style.width = '0%';
|
||||||
|
}
|
||||||
|
}, 300);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load content dynamically
|
||||||
|
async loadContent(content, title = null) {
|
||||||
|
//console.log('Router: Loading content with title:', title);
|
||||||
|
if (!this.contentContainer) {
|
||||||
|
console.error('Router: No content container found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear current content
|
||||||
|
this.contentContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Update page title
|
||||||
|
if (title) {
|
||||||
|
document.title = `${title} - LibrePortal`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new content
|
||||||
|
this.contentContainer.innerHTML = content;
|
||||||
|
//console.log('Router: Content loaded successfully, container innerHTML length:', this.contentContainer.innerHTML.length);
|
||||||
|
|
||||||
|
// Update active nav state
|
||||||
|
this.updateActiveNav();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateActiveNav() {
|
||||||
|
// Always clear all navigation first to prevent sticky highlighting
|
||||||
|
if (typeof window.topbarNavigationHighlighting === 'function') {
|
||||||
|
window.topbarNavigationHighlighting();
|
||||||
|
return; // Exit early - let topbar handle it
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to original logic if helper not available
|
||||||
|
// Remove active class from all nav items
|
||||||
|
document.querySelectorAll('.nav-item').forEach(item => {
|
||||||
|
item.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use path-based detection only for consistency
|
||||||
|
const pathname = window.location.pathname;
|
||||||
|
|
||||||
|
let activeNavId;
|
||||||
|
|
||||||
|
// PRIMARY: Use path-based detection only (most reliable)
|
||||||
|
if (pathname.startsWith('/app') || pathname.startsWith('/apps')) {
|
||||||
|
activeNavId = 'nav-app-center';
|
||||||
|
} else if (pathname.startsWith('/admin') || pathname.startsWith('/config') || pathname.startsWith('/ssh')) {
|
||||||
|
activeNavId = 'nav-config';
|
||||||
|
} else if (pathname.startsWith('/tasks')) {
|
||||||
|
activeNavId = 'nav-tasks';
|
||||||
|
} else if (pathname === '/' || pathname === '/dashboard') {
|
||||||
|
activeNavId = 'nav-dashboard';
|
||||||
|
} else {
|
||||||
|
activeNavId = 'nav-dashboard'; // default
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeNavId) {
|
||||||
|
const activeNav = document.getElementById(activeNavId);
|
||||||
|
if (activeNav) {
|
||||||
|
activeNav.classList.add('nav-active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global router instance
|
||||||
|
const router = new Router();
|
||||||
@ -1,8 +1,99 @@
|
|||||||
// App metadata helper — resolves an app's icon path, falling back to the
|
// UI Helper Functions - Shared across all pages
|
||||||
// bundled SVG when the catalog entry doesn't carry an explicit icon URL.
|
// Simple functions without import/export syntax for compatibility
|
||||||
|
|
||||||
|
// App icon and name helpers
|
||||||
function getAppIcon(appName, appIconUrl) {
|
function getAppIcon(appName, appIconUrl) {
|
||||||
const cleanAppName = appName.replace('install_', '').replace(' ', '');
|
const cleanAppName = appName.replace('install_', '').replace(' ', '');
|
||||||
// Use icon URL from JSON if available, otherwise fall back to default
|
// Use icon URL from JSON if available, otherwise fall back to default
|
||||||
return appIconUrl || `/core/icons/apps/${cleanAppName}.svg`;
|
return appIconUrl || `/core/icons/apps/${cleanAppName}.svg`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAppStatus(installed) {
|
||||||
|
return installed ? 'Installed' : 'Not Installed';
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAppName(name) {
|
||||||
|
return name.split(' - ')[0].split(' -')[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppShortName(appData) {
|
||||||
|
return appData.name.split(' - ')[0].split(' -')[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile menu helpers
|
||||||
|
function setupMobileMenu() {
|
||||||
|
const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
|
||||||
|
const sidebar = document.getElementById('sidebar');
|
||||||
|
const mobileOverlay = document.getElementById('mobile-overlay');
|
||||||
|
|
||||||
|
if (!mobileMenuToggle || !sidebar || !mobileOverlay) {
|
||||||
|
//console.log('Mobile menu elements not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleMobileMenu() {
|
||||||
|
sidebar.classList.toggle('mobile-open');
|
||||||
|
mobileOverlay.classList.toggle('active');
|
||||||
|
|
||||||
|
if (sidebar.classList.contains('mobile-open')) {
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
} else {
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeMobileMenu() {
|
||||||
|
sidebar.classList.remove('mobile-open');
|
||||||
|
mobileOverlay.classList.remove('active');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile menu event listeners
|
||||||
|
mobileMenuToggle.addEventListener('click', toggleMobileMenu);
|
||||||
|
mobileOverlay.addEventListener('click', closeMobileMenu);
|
||||||
|
|
||||||
|
// Close mobile menu when window is resized to desktop size
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
if (window.innerWidth > 768) {
|
||||||
|
closeMobileMenu();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Expose closeMobileMenu globally for other scripts
|
||||||
|
window.closeMobileMenu = closeMobileMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigation helpers
|
||||||
|
function setupActiveNavigation() {
|
||||||
|
const navItems = document.querySelectorAll('.nav-item');
|
||||||
|
navItems.forEach(item => {
|
||||||
|
if (item.textContent.includes('App Center')) {
|
||||||
|
item.classList.add('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// DOM helpers
|
||||||
|
function safeGetElement(id) {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (!element) {
|
||||||
|
//console.log(`${id} element not found`);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeQuerySelector(selector) {
|
||||||
|
const element = document.querySelector(selector);
|
||||||
|
if (!element) {
|
||||||
|
//console.log(`${selector} element not found`);
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function safeQuerySelectorAll(selector) {
|
||||||
|
const elements = document.querySelectorAll(selector);
|
||||||
|
if (!elements || elements.length === 0) {
|
||||||
|
//console.log(`${selector} elements not found`);
|
||||||
|
}
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|||||||
@ -481,4 +481,97 @@ class TopbarComponent {
|
|||||||
document.body.setAttribute('data-theme', theme);
|
document.body.setAttribute('data-theme', theme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static clearAllNavigationHighlighting() {
|
||||||
|
//// // console.log('🧹 Aggressively clearing all navigation highlighting');
|
||||||
|
|
||||||
|
// Remove ALL active classes from ALL navigation items
|
||||||
|
document.querySelectorAll('.nav-item').forEach(item => {
|
||||||
|
//// // console.log('🧹 Clearing nav item:', item.id, item.textContent.trim());
|
||||||
|
item.classList.remove('active');
|
||||||
|
item.classList.remove('nav-active');
|
||||||
|
// Also remove any other possible active states
|
||||||
|
item.removeAttribute('aria-current');
|
||||||
|
item.blur(); // Remove focus
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also clear other active classes that might interfere
|
||||||
|
document.querySelectorAll('.category.active').forEach(item => {
|
||||||
|
item.classList.remove('active');
|
||||||
|
});
|
||||||
|
document.querySelectorAll('.tab-button.active').forEach(item => {
|
||||||
|
item.classList.remove('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
//// // console.log('🧹 Cleared all navigation highlighting');
|
||||||
|
}
|
||||||
|
|
||||||
|
static createNavigationHighlighting() {
|
||||||
|
// Define the single function that handles navigation highlighting
|
||||||
|
window.topbarNavigationHighlighting = function() {
|
||||||
|
// Always clear all existing navigation first to prevent sticky highlighting
|
||||||
|
TopbarComponent.clearAllNavigationHighlighting();
|
||||||
|
|
||||||
|
// PRIMARY: Use path-based detection only (most reliable)
|
||||||
|
// This avoids confusion from query parameters like tab=, app=, etc.
|
||||||
|
const path = window.location.pathname;
|
||||||
|
let activeNavId;
|
||||||
|
|
||||||
|
if (path.startsWith('/app') || path.startsWith('/apps')) {
|
||||||
|
activeNavId = 'nav-app-center';
|
||||||
|
} else if (path.startsWith('/admin') || path.startsWith('/config') || path.startsWith('/ssh')) {
|
||||||
|
activeNavId = 'nav-config';
|
||||||
|
} else if (path.startsWith('/tasks')) {
|
||||||
|
activeNavId = 'nav-tasks';
|
||||||
|
} else if (path.startsWith('/backup')) {
|
||||||
|
activeNavId = 'nav-backup';
|
||||||
|
} else if (path.startsWith('/updater')) {
|
||||||
|
activeNavId = 'nav-updater';
|
||||||
|
} else if (path === '/' || path === '/dashboard') {
|
||||||
|
activeNavId = 'nav-dashboard';
|
||||||
|
} else {
|
||||||
|
// Fallback: use currentPage detection
|
||||||
|
const currentPage = new TopbarComponent().getCurrentPage();
|
||||||
|
switch (currentPage) {
|
||||||
|
case 'index.html':
|
||||||
|
case '':
|
||||||
|
case 'index':
|
||||||
|
case 'app':
|
||||||
|
case 'apps':
|
||||||
|
activeNavId = 'nav-app-center';
|
||||||
|
break;
|
||||||
|
case 'config':
|
||||||
|
activeNavId = 'nav-config';
|
||||||
|
break;
|
||||||
|
case 'tasks':
|
||||||
|
activeNavId = 'nav-tasks';
|
||||||
|
break;
|
||||||
|
case 'backup':
|
||||||
|
activeNavId = 'nav-backup';
|
||||||
|
break;
|
||||||
|
case 'updater':
|
||||||
|
activeNavId = 'nav-updater';
|
||||||
|
break;
|
||||||
|
case 'dashboard':
|
||||||
|
activeNavId = 'nav-dashboard';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
activeNavId = 'nav-dashboard';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add active class to current page nav
|
||||||
|
if (activeNavId) {
|
||||||
|
const activeNav = document.getElementById(activeNavId);
|
||||||
|
if (activeNav) {
|
||||||
|
activeNav.classList.add('nav-active');
|
||||||
|
} else {
|
||||||
|
console.warn(`❌ Topbar: Nav element not found: ${activeNavId}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`❌ Topbar: Could not determine active nav for path: ${path}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//// // console.log('🌐 Topbar navigation highlighting function created');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -92,6 +92,7 @@
|
|||||||
<script src="/core/lib/util/lp-ui.js"></script>
|
<script src="/core/lib/util/lp-ui.js"></script>
|
||||||
<script src="/core/lib/util/dom-helpers.js"></script>
|
<script src="/core/lib/util/dom-helpers.js"></script>
|
||||||
<script src="/core/lib/util/ui-helpers.js"></script>
|
<script src="/core/lib/util/ui-helpers.js"></script>
|
||||||
|
<script src="/core/lib/util/router.js"></script>
|
||||||
<script src="/core/lib/util/data-loader.js"></script>
|
<script src="/core/lib/util/data-loader.js"></script>
|
||||||
<script src="/core/lib/util/system-live.js"></script>
|
<script src="/core/lib/util/system-live.js"></script>
|
||||||
<script src="/core/lib/util/dismissible.js"></script>
|
<script src="/core/lib/util/dismissible.js"></script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user