dashboard_for_sage/wwwroot/shell_theme.js
yumoqing e7045ab6ba fix(shell): sageReloadMenu always sets sidebar width and updates toggle icon
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.
2026-07-27 11:12:37 +08:00

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;
})();