Previously sageReloadMenu only set sidebar width when collapsed (64px). When expanded, the inline width was never restored, leaving the sidebar potentially stuck at collapsed width after menu rebuild. The menu text widgets (filler class with overflow:hidden) would clip labels to ~1 char. Now both collapsed (64px) and expanded (240px) states explicitly set sidebar.el.style.width and updateSidebarIcon after menu rebuild.
264 lines
9.4 KiB
JavaScript
264 lines
9.4 KiB
JavaScript
/* Sage Modern UI Shell Theme & Layout Controller
|
|
Handles: theme switching (dark/light), sidebar collapse, localStorage persistence
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
var THEME_KEY = 'sage_ui_theme';
|
|
var SIDEBAR_KEY = 'sage_sidebar_collapsed';
|
|
|
|
// Initialize theme on page load
|
|
function initTheme() {
|
|
var saved = null;
|
|
try { saved = localStorage.getItem(THEME_KEY); } catch(e) {}
|
|
var theme = saved || 'dark';
|
|
document.documentElement.setAttribute('data-theme', theme);
|
|
updateThemeIcon(theme);
|
|
}
|
|
|
|
// Toggle between dark and light
|
|
function toggleTheme() {
|
|
var current = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
var next = current === 'dark' ? 'light' : 'dark';
|
|
document.documentElement.setAttribute('data-theme', next);
|
|
try { localStorage.setItem(THEME_KEY, next); } catch(e) {}
|
|
updateThemeIcon(next);
|
|
}
|
|
|
|
// Update the theme toggle icon based on current theme
|
|
function updateThemeIcon(theme) {
|
|
var btn = document.getElementById('theme_toggle_btn');
|
|
if (!btn) return;
|
|
if (theme === 'light') {
|
|
// Show moon icon (switch to dark)
|
|
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>';
|
|
} else {
|
|
// Show sun icon (switch to light)
|
|
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>';
|
|
}
|
|
}
|
|
|
|
// Initialize sidebar state
|
|
function initSidebar() {
|
|
var collapsed = false;
|
|
try { collapsed = localStorage.getItem(SIDEBAR_KEY) === 'true'; } catch(e) {}
|
|
|
|
// Auto-collapse on mobile
|
|
if (isMobile()) {
|
|
collapsed = true;
|
|
}
|
|
|
|
var sidebar = document.getElementById('sage_sidebar');
|
|
if (sidebar && collapsed) {
|
|
sidebar.classList.add('collapsed');
|
|
sidebar.style.width = '64px';
|
|
}
|
|
}
|
|
|
|
// Handle window resize - auto-collapse on mobile
|
|
function handleResize() {
|
|
var sidebar = document.getElementById('sage_sidebar');
|
|
if (!sidebar) return;
|
|
|
|
if (isMobile() && !sidebar.classList.contains('collapsed')) {
|
|
sidebar.classList.add('collapsed');
|
|
sidebar.style.width = '64px';
|
|
try { localStorage.setItem(SIDEBAR_KEY, 'true'); } catch(e) {}
|
|
updateSidebarIcon(true);
|
|
}
|
|
}
|
|
|
|
// Check if we're on mobile viewport
|
|
function isMobile() {
|
|
return window.innerWidth <= 768;
|
|
}
|
|
|
|
// Close sidebar when clicking outside on mobile
|
|
function setupMobileOverlay() {
|
|
document.addEventListener('click', function(e) {
|
|
if (!isMobile()) return;
|
|
var sidebar = document.getElementById('sage_sidebar');
|
|
var toggleBtn = document.getElementById('sidebar_toggle_btn');
|
|
if (!sidebar || sidebar.classList.contains('collapsed')) return;
|
|
// If click is outside sidebar and toggle button, close sidebar
|
|
if (!sidebar.contains(e.target) && (!toggleBtn || !toggleBtn.contains(e.target))) {
|
|
sidebar.classList.add('collapsed');
|
|
sidebar.style.width = '64px';
|
|
try { localStorage.setItem(SIDEBAR_KEY, 'true'); } catch(ex) {}
|
|
updateSidebarIcon(true);
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateSidebarIcon(isCollapsed) {
|
|
var btn = document.getElementById('sidebar_toggle_btn');
|
|
if (!btn) return;
|
|
if (isCollapsed) {
|
|
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="13 17 18 12 13 7"/><polyline points="6 17 11 12 6 7"/></svg>';
|
|
} else {
|
|
btn.innerHTML = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="11 17 6 12 11 7"/><polyline points="18 17 13 12 18 7"/></svg>';
|
|
}
|
|
}
|
|
|
|
// Toggle sidebar collapse
|
|
function toggleSidebar() {
|
|
var sidebar = document.getElementById('sage_sidebar');
|
|
if (!sidebar) return;
|
|
sidebar.classList.toggle('collapsed');
|
|
var isCollapsed = sidebar.classList.contains('collapsed');
|
|
try { localStorage.setItem(SIDEBAR_KEY, isCollapsed); } catch(e) {}
|
|
updateSidebarIcon(isCollapsed);
|
|
// Override inline width style (bricks sets inline style which beats CSS class)
|
|
sidebar.style.width = isCollapsed ? '64px' : '240px';
|
|
// Toggle Menu widget collapse state
|
|
if (typeof bricks !== 'undefined') {
|
|
var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
|
|
if (menu && menu.toggle_collapse) {
|
|
menu.toggle_collapse();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Initialize SPA Router
|
|
function initRouter() {
|
|
if (typeof bricks === 'undefined' || !bricks.Router) {
|
|
console.log('[Shell] Router not available');
|
|
return;
|
|
}
|
|
bricks.Router.init({
|
|
targets: [
|
|
{ id: 'sage_main_content', param: 'page' }
|
|
]
|
|
});
|
|
}
|
|
|
|
// Called by user_logined event — skip dashboard if Router has a saved route
|
|
window.sageOnLogin = async function(dashboardUrl) {
|
|
var target = bricks.getWidgetById('sage_main_content', bricks.app);
|
|
if (!target) return;
|
|
|
|
var hasRoute = false;
|
|
if (bricks.Router && bricks.Router._enabled) {
|
|
hasRoute = bricks.Router.current('sage_main_content')
|
|
|| new URLSearchParams(window.location.search).has('page');
|
|
}
|
|
|
|
if (hasRoute) {
|
|
console.log('[Shell] Router has route, skip dashboard load');
|
|
return;
|
|
}
|
|
|
|
// No route — load dashboard via Router
|
|
if (dashboardUrl && bricks.Router && bricks.Router._enabled) {
|
|
bricks.Router.navigate('sage_main_content', dashboardUrl);
|
|
} else if (dashboardUrl) {
|
|
var desc = { widgettype: 'urlwidget', options: { url: dashboardUrl } };
|
|
var w = await bricks.widgetBuild(desc, bricks.app);
|
|
if (w) {
|
|
target.clear_widgets();
|
|
target.add_widget(w);
|
|
}
|
|
}
|
|
};
|
|
|
|
// Reload global menu after login/logout
|
|
window.sageReloadMenu = async function() {
|
|
if (typeof bricks === 'undefined') return;
|
|
var sidebar = bricks.getWidgetById('sage_sidebar', bricks.app);
|
|
if (!sidebar) {
|
|
console.log('[Shell] sage_sidebar not found');
|
|
return;
|
|
}
|
|
// Check if sidebar is currently collapsed before clearing
|
|
var isCollapsed = sidebar.el && sidebar.el.classList.contains('collapsed');
|
|
// Clear existing children
|
|
sidebar.subwidgets.forEach(function(w) { w.destroy && w.destroy(); });
|
|
sidebar.subwidgets = [];
|
|
sidebar.el.innerHTML = '';
|
|
// Rebuild menu urlwidget
|
|
var menuUrl = bricks.app.baseUrl + '/global_menu.ui?_webbricks_=1';
|
|
var desc = {
|
|
"widgettype": "urlwidget",
|
|
"options": { "url": menuUrl }
|
|
};
|
|
try {
|
|
var w = await bricks.widgetBuild(desc, sidebar);
|
|
if (w) {
|
|
sidebar.addSubWidget(w);
|
|
// Re-apply sidebar width and collapsed state to newly built menu
|
|
sidebar.el.style.width = isCollapsed ? '64px' : '240px';
|
|
updateSidebarIcon(isCollapsed);
|
|
if (isCollapsed) {
|
|
var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
|
|
if (menu && menu.collapse) {
|
|
menu.collapse();
|
|
}
|
|
}
|
|
console.log('[Shell] Menu reloaded');
|
|
}
|
|
} catch(e) {
|
|
console.log('[Shell] Menu reload error:', e);
|
|
}
|
|
};
|
|
|
|
// Run on DOM ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initTheme();
|
|
initSidebar();
|
|
initRouter();
|
|
setupMobileOverlay();
|
|
window.addEventListener('resize', handleResize);
|
|
});
|
|
} else {
|
|
initTheme();
|
|
initSidebar();
|
|
initRouter();
|
|
setupMobileOverlay();
|
|
window.addEventListener('resize', handleResize);
|
|
}
|
|
|
|
// Bricks widgets render asynchronously after DOMContentLoaded.
|
|
// Retry updateThemeIcon until the button element exists.
|
|
(function retryThemeIcon() {
|
|
var btn = document.getElementById('theme_toggle_btn');
|
|
if (btn) {
|
|
var theme = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
updateThemeIcon(theme);
|
|
} else {
|
|
setTimeout(retryThemeIcon, 200);
|
|
}
|
|
})();
|
|
|
|
// Retry sidebar icon init until the button element exists
|
|
(function retrySidebarIcon() {
|
|
var sidebar = document.getElementById('sage_sidebar');
|
|
var btn = document.getElementById('sidebar_toggle_btn');
|
|
if (btn && sidebar) {
|
|
var isCollapsed = sidebar.classList.contains('collapsed');
|
|
updateSidebarIcon(isCollapsed);
|
|
// Sync inline width with collapsed state (bricks may have overridden it)
|
|
sidebar.style.width = isCollapsed ? '64px' : '240px';
|
|
// Apply collapsed state to Menu if sidebar was already collapsed
|
|
if (isCollapsed && typeof bricks !== 'undefined') {
|
|
var menu = bricks.getWidgetById('global_nav_menu', bricks.app);
|
|
if (menu && menu.collapse) {
|
|
menu.collapse();
|
|
} else {
|
|
setTimeout(retrySidebarIcon, 200);
|
|
return;
|
|
}
|
|
}
|
|
} else {
|
|
setTimeout(retrySidebarIcon, 200);
|
|
}
|
|
})();
|
|
|
|
// Expose global functions for bricks bind access
|
|
window.sageToggleTheme = toggleTheme;
|
|
window.sageToggleSidebar = toggleSidebar;
|
|
window.sageInitTheme = initTheme;
|
|
window.sageInitSidebar = initSidebar;
|
|
|
|
})();
|