Files
BetterDesk/web-nodejs/public/js/app.js
T
UNITRONIX c5970657e7 feat(i18n): expose all panel languages in selector
Render language choices dynamically from loaded locale files instead of hardcoding EN/PL. Add selectors on the navbar, login screen, and desktop login screen so users can choose a language before or after authentication.

Persist the selected language in the browser via cookie/localStorage and on authenticated accounts via users.preferred_language. Merge client translations with the default locale fallback so incomplete language files do not surface raw keys in the UI.
2026-05-03 02:02:15 +02:00

207 lines
6.4 KiB
JavaScript

/**
* BetterDesk Console - Main Application
*/
(function() {
'use strict';
// Initialize when DOM is ready
document.addEventListener('DOMContentLoaded', init);
function init() {
initSidebar();
initNavbar();
initLanguageSelector();
initUserMenu();
initRefreshButton();
initRegistrationBadge();
initDesktopMode();
}
/**
* Sidebar functionality (mobile support)
*/
function initSidebar() {
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebar-overlay');
if (!sidebar) return;
// Close sidebar on overlay click (mobile)
overlay?.addEventListener('click', () => {
sidebar.classList.remove('open');
});
// Mobile menu button
const mobileMenuBtn = document.getElementById('mobile-menu-btn');
if (mobileMenuBtn) {
mobileMenuBtn.addEventListener('click', () => {
sidebar.classList.toggle('open');
});
// Show mobile menu button on small screens
function checkMobile() {
mobileMenuBtn.style.display = window.innerWidth <= 1024 ? 'flex' : 'none';
}
checkMobile();
window.addEventListener('resize', Utils.debounce(checkMobile, 200));
}
}
/**
* Navbar functionality
*/
function initNavbar() {
// Dropdowns
document.querySelectorAll('.lang-selector').forEach(selector => {
const btn = selector.querySelector('button');
const dropdown = selector.querySelector('.lang-dropdown');
if (!btn || !dropdown) return;
btn.addEventListener('click', (e) => {
e.stopPropagation();
// Close other dropdowns
document.querySelectorAll('.lang-dropdown.open').forEach(d => {
if (d !== dropdown) d.classList.remove('open');
});
dropdown.classList.toggle('open');
});
});
// Close dropdowns on outside click
document.addEventListener('click', () => {
document.querySelectorAll('.lang-dropdown.open').forEach(d => {
d.classList.remove('open');
});
});
}
/**
* Language selector
*/
function initLanguageSelector() {
const langDropdown = document.getElementById('lang-dropdown');
if (!langDropdown) return;
langDropdown.querySelectorAll('.lang-option').forEach(option => {
option.addEventListener('click', async (e) => {
const lang = option.dataset.lang;
if (lang) {
await window.changeLanguage(lang);
}
});
option.addEventListener('keydown', async (e) => {
if (e.key !== 'Enter' && e.key !== ' ') return;
e.preventDefault();
const lang = option.dataset.lang;
if (lang) {
await window.changeLanguage(lang);
}
});
});
}
/**
* User menu
*/
function initUserMenu() {
const changePasswordBtn = document.getElementById('change-password-btn');
const logoutBtn = document.getElementById('logout-btn');
changePasswordBtn?.addEventListener('click', () => {
window.location.href = '/settings';
});
logoutBtn?.addEventListener('click', async () => {
const confirmed = await Modal.confirm({
title: _('auth.logout'),
message: _('auth.logout_confirm'),
confirmLabel: _('auth.logout'),
danger: true
});
if (confirmed) {
try {
await Utils.api('/api/auth/logout', { method: 'POST' });
window.location.href = '/login';
} catch (error) {
Notifications.error(_('errors.logout_failed'));
}
}
});
}
/**
* Refresh button
*/
function initRefreshButton() {
const refreshBtn = document.getElementById('refresh-btn');
if (!refreshBtn) return;
refreshBtn.addEventListener('click', () => {
// Dispatch custom event for page-specific refresh handlers
window.dispatchEvent(new CustomEvent('app:refresh'));
// Visual feedback
const icon = refreshBtn.querySelector('.material-icons');
if (icon) {
icon.style.animation = 'spin 0.5s linear';
setTimeout(() => {
icon.style.animation = '';
}, 500);
}
});
}
// Add spin animation
const style = document.createElement('style');
style.textContent = `
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
`;
document.head.appendChild(style);
/**
* Periodically fetch pending registration count for sidebar badge.
*/
function initRegistrationBadge() {
const badge = document.getElementById('reg-sidebar-badge');
if (!badge) return;
let badgeInterval = null;
async function updateBadge() {
try {
const resp = await fetch('/api/registrations/count');
if (!resp.ok) {
if (resp.status === 401 && badgeInterval) {
clearInterval(badgeInterval);
badgeInterval = null;
}
return;
}
const data = await resp.json();
const count = data.count || 0;
badge.textContent = count;
badge.style.display = count > 0 ? '' : 'none';
} catch (_) { /* silent */ }
}
updateBadge();
badgeInterval = setInterval(updateBadge, 30000); // every 30s
}
/**
* Desktop mode initialization
*/
function initDesktopMode() {
if (window.DesktopMode && typeof window.DesktopMode.init === 'function') {
window.DesktopMode.init();
}
}
})();