import { LANGUAGE_ICON, FRIENDS_ICON } from './screens/icons.js'; import { LANGUAGES, languageLabel } from './languages.js'; import { getSettings, updateSettings } from './settings.js'; import { renderThemeButton } from './themeButton.js'; import { getUser, onAuthChange } from './auth.js'; import { onCountsChange, getCounts } from './counts.js'; import { attachTooltips } from './tooltip.js'; import { escapeHtml } from './util.js'; // The top-right cluster: the typing language, the theme, and who you are -- // Friends sits directly beside the account control because the two are the // same thing, your identity and who it connects you to. These are the app's own chrome -- // they belong together and away from the bottom rail's per-screen // navigation. Screens with their own top-right controls (the typing screen's // Restart and Zen Finish) pass them in as `extras` so everything shares one // row instead of each fixed-positioning itself into a collision. export function renderTopRail(root, { onShowAccount, onShowFriends, extras = [] } = {}) { const wrap = document.createElement('div'); wrap.className = 'top-rail'; root.appendChild(wrap); for (const el of extras) wrap.appendChild(el); const langGroup = document.createElement('div'); langGroup.className = 'lang-group'; langGroup.innerHTML = ` `; wrap.appendChild(langGroup); const cleanupTheme = renderThemeButton(wrap); const friends = document.createElement('div'); friends.className = 'friends-control'; if (onShowFriends) { friends.innerHTML = ` `; } wrap.appendChild(friends); const auth = document.createElement('div'); auth.className = 'auth-control'; wrap.appendChild(auth); attachTooltips(wrap); // Language ------------------------------------------------------------ const langPopover = langGroup.querySelector('.lang-popover'); function paintLang() { const current = getSettings().language || 'en'; langGroup.querySelectorAll('.lang-item').forEach(item => { item.classList.toggle('active', item.dataset.lang === current); }); langGroup.querySelector('[data-action="lang"]').dataset.tooltip = `Typing language: ${languageLabel(current)}`; } paintLang(); langGroup.querySelector('[data-action="lang"]').addEventListener('click', e => { e.stopPropagation(); langPopover.hidden = !langPopover.hidden; }); langGroup.querySelectorAll('.lang-item').forEach(item => { item.addEventListener('click', () => { updateSettings({ language: item.dataset.lang }); paintLang(); langPopover.hidden = true; }); }); // Identity ------------------------------------------------------------ function paintAuth() { const user = getUser(); auth.innerHTML = user ? `` : ` `; attachTooltips(auth); auth.querySelectorAll('button').forEach(btn => { btn.addEventListener('click', () => onShowAccount?.()); }); } paintAuth(); const unsubscribeAuth = onAuthChange(paintAuth); // Friends ------------------------------------------------------------ const friendsBtn = friends.querySelector('[data-action="rail-friends"]'); if (friendsBtn) friendsBtn.addEventListener('click', onShowFriends); // How many friends are online, not how many you have - a total is a fact // about your account, whereas "2 online" is a reason to open the screen. const friendsOnlineEl = friends.querySelector('[data-badge="friends-online"]'); function paintCounts({ friends: friendCount, friendsOnline }) { if (friendsBtn) { friendsBtn.dataset.tooltip = !friendCount ? 'Friends' : (friendsOnline ? `Friends - ${friendsOnline} of ${friendCount} online` : `Friends - ${friendCount}, none online`); } if (!friendsOnlineEl) return; friendsOnlineEl.hidden = !friendsOnline; friendsOnlineEl.textContent = `${friendsOnline} online`; } paintCounts(getCounts()); const unsubscribeCounts = onCountsChange(paintCounts); const handleOutsideClick = e => { if (!langPopover.hidden && !langGroup.contains(e.target)) langPopover.hidden = true; }; document.addEventListener('click', handleOutsideClick); return () => { unsubscribeCounts(); document.removeEventListener('click', handleOutsideClick); unsubscribeAuth(); cleanupTheme(); wrap.remove(); }; }