Enhance UI components and add Theme/Navigation Managers:

- Refactor `app_context.js` for async context loading and translation handling.
- Introduce `ThemeManager` for dynamic theme control.
- Add `NavigationManager` for improved section navigation.
- Expose new `/api/ui-context` endpoint for UI context data.
This commit is contained in:
2026-01-09 20:34:32 +00:00
parent 90f4424f41
commit cf994dbf3b
5 changed files with 151 additions and 3 deletions

View File

@@ -369,6 +369,11 @@ async def update_settings(request: Request, username: str = Depends(get_current_
return RedirectResponse(url="/", status_code=303)
@app.get("/api/ui-context", response_class=JSONResponse)
async def get_ui_context(username: str = Depends(get_current_user)):
ui_messages, ui_lang = get_ui_messages()
return {"lang": ui_lang, "messages": ui_messages}
@app.get("/api/status")
async def get_status(username: str = Depends(get_current_user)):
return {

View File

@@ -2,7 +2,7 @@
* AutoMetabuilder - Shared Context
*/
(() => {
const translations = window.AMB_I18N || {};
let translations = {};
const t = (key, fallback = '') => translations[key] || fallback || key;
const format = (text, values = {}) => text.replace(/\{(\w+)\}/g, (_, name) => values[name] ?? '');
const authHeaders = (() => {
@@ -13,9 +13,31 @@
return { Authorization: `Basic ${token}` };
})();
window.AMBContext = {
const context = {
t,
format,
authHeaders
authHeaders,
lang: 'en',
ready: null
};
const loadContext = async () => {
try {
const response = await fetch('/api/ui-context', {
credentials: 'include',
headers: authHeaders || {}
});
if (!response.ok) {
throw new Error(`UI context fetch failed: ${response.status}`);
}
const data = await response.json();
translations = data.messages || {};
context.lang = data.lang || 'en';
} catch (error) {
console.error('Failed to load UI context', error);
}
};
context.ready = loadContext();
window.AMBContext = context;
})();

View File

@@ -10,6 +10,9 @@
};
const initAll = async () => {
if (window.AMBContext?.ready) {
await window.AMBContext.ready;
}
for (const plugin of plugins) {
try {
await plugin.init();

View File

@@ -0,0 +1,73 @@
/**
* AutoMetabuilder - Navigation Manager
*/
(() => {
const NavigationManager = {
_popstateBound: false,
init() {
this.bindLinks();
this.activateFromHash(false);
if (!this._popstateBound) {
window.addEventListener('popstate', () => {
this.activateFromHash(false);
});
this._popstateBound = true;
}
},
bindLinks() {
document.querySelectorAll('[data-section]').forEach(link => {
if (link.dataset.navBound === 'true') return;
link.dataset.navBound = 'true';
link.addEventListener('click', event => {
event.preventDefault();
this.showSection(link.dataset.section);
});
});
},
refresh() {
this.bindLinks();
if (!this.activateFromHash(false)) {
const firstLink = document.querySelector('[data-section]');
if (firstLink) {
this.showSection(firstLink.dataset.section, false);
}
}
},
activateFromHash(updateHistory) {
const hash = window.location.hash.slice(1);
if (!hash || !document.querySelector(`#${hash}`)) return false;
this.showSection(hash, updateHistory);
return true;
},
showSection(sectionId, updateHistory = true) {
document.querySelectorAll('.amb-section').forEach(section => {
section.classList.remove('active');
});
const targetSection = document.querySelector(`#${sectionId}`);
if (targetSection) {
targetSection.classList.add('active');
}
document.querySelectorAll('.amb-nav-link').forEach(link => {
link.classList.remove('active');
});
const activeLink = document.querySelector(`[data-section="${sectionId}"]`);
if (activeLink) {
activeLink.classList.add('active');
}
if (updateHistory) {
history.pushState(null, '', `#${sectionId}`);
}
}
};
window.NavigationManager = NavigationManager;
window.AMBPlugins?.register('navigation_manager', async () => NavigationManager.init());
})();

View File

@@ -0,0 +1,45 @@
/**
* AutoMetabuilder - Theme Manager
*/
(() => {
const ThemeManager = {
STORAGE_KEY: 'amb-theme',
init() {
const saved = localStorage.getItem(this.STORAGE_KEY);
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
const theme = saved || (prefersDark ? 'dark' : 'light');
this.setTheme(theme);
document.querySelectorAll('[data-theme-toggle]').forEach(btn => {
btn.addEventListener('click', () => this.toggle());
});
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
if (!localStorage.getItem(this.STORAGE_KEY)) {
this.setTheme(e.matches ? 'dark' : 'light');
}
});
},
setTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
localStorage.setItem(this.STORAGE_KEY, theme);
this.updateToggleIcon(theme);
},
toggle() {
const current = document.documentElement.getAttribute('data-theme');
this.setTheme(current === 'dark' ? 'light' : 'dark');
},
updateToggleIcon(theme) {
document.querySelectorAll('[data-theme-toggle] i').forEach(icon => {
icon.className = theme === 'dark' ? 'bi bi-moon-fill' : 'bi bi-sun-fill';
});
}
};
window.ThemeManager = ThemeManager;
window.AMBPlugins?.register('theme_manager', async () => ThemeManager.init());
})();