Files
SHiNE-server/shine-UI/js/components/toolbar.js
T
2026-09-22 14:09:02 +03:00

160 lines
6.8 KiB
JavaScript

import { resolveToolbarActive } from '../router.js';
import { state, authService } from '../state.js';
import { openAuthRequiredModal } from '../services/auth-required-modal.js';
import { iconHtml as lineIcon } from './ui-icon.js';
const ITEMS = [
{ pageId: 'messages-list', label: 'Личные' },
{ pageId: 'channels-list', label: 'Каналы' },
{ pageId: 'network-view', label: 'Связи' },
{ pageId: 'notifications-view', label: 'Уведомления' },
{ pageId: 'profile-view', label: 'Профиль' },
];
function iconHtml(item) {
const names = { 'messages-list': 'message', 'channels-list': 'channels', 'network-view': 'network', 'notifications-view': 'bell', 'profile-view': 'profile' };
return lineIcon(names[item.pageId]);
}
function normalizeCounters(payload = {}) {
const notifications = payload?.notifications || {};
const notificationTotal = Math.max(0, Number(payload?.notificationsUnreadCount ?? (
Number(notifications.replies || 0) + Number(notifications.connections || 0) + Number(notifications.events || 0)
)) || 0);
return {
dmUnreadCount: Math.max(0, Number(payload?.dmUnreadCount || 0) || 0),
channelsUnreadCount: Math.max(0, Number(payload?.channelsUnreadCount || 0) || 0),
notificationsUnreadCount: notificationTotal,
notifications: {
replies: Math.max(0, Number(notifications.replies ?? payload?.notificationRepliesUnreadCount ?? 0) || 0),
connections: Math.max(0, Number(notifications.connections ?? payload?.notificationConnectionsUnreadCount ?? 0) || 0),
events: Math.max(0, Number(notifications.events ?? payload?.notificationEventsUnreadCount ?? 0) || 0),
},
};
}
function setCounterState(payload = {}) {
state.userCounters = normalizeCounters(payload);
state.notificationUnreadTotal = state.userCounters.notificationsUnreadCount;
}
function renderBadge(btn, count, ariaLabel, extraClass = '') {
if (!btn) return;
let badge = btn.querySelector('.toolbar-unread-badge');
if (count <= 0) { badge?.remove(); return; }
if (!badge) {
badge = document.createElement('span');
badge.className = `toolbar-unread-badge${extraClass ? ` ${extraClass}` : ''}`;
btn.append(badge);
}
badge.textContent = count > 99 ? '99+' : String(count);
badge.setAttribute('aria-label', `${ariaLabel}: ${count}`);
}
function applyCountersToMountedToolbars() {
const c = normalizeCounters(state.userCounters);
document.querySelectorAll('.toolbar').forEach((toolbar) => {
renderBadge(toolbar.querySelector('[data-toolbar-page="messages-list"]'), c.dmUnreadCount, 'Непрочитанных личных сообщений');
renderBadge(toolbar.querySelector('[data-toolbar-page="channels-list"]'), c.channelsUnreadCount, 'Непрочитанных сообщений в каналах');
renderBadge(toolbar.querySelector('[data-toolbar-page="notifications-view"]'), c.notificationsUnreadCount, 'Новых уведомлений', 'notification-toolbar-badge');
});
}
let countersPushBound = false;
function ensureCountersPushBound() {
if (countersPushBound) return;
countersPushBound = true;
authService.onEvent('UserCountersChanged', (event) => {
setCounterState(event?.payload || {});
applyCountersToMountedToolbars();
});
}
async function refreshUserCounters() {
if (!state.session.isAuthorized) return;
try {
setCounterState(await authService.getUserCounters());
applyCountersToMountedToolbars();
} catch {
// Keep the last known counters; realtime push or the next refresh can recover.
}
}
function navigateWithGuestRules(pageId, navigate) {
if (state.session.isAuthorized) {
navigate(pageId);
return;
}
if (pageId === 'messages-list') {
openAuthRequiredModal({
title: 'Личные сообщения недоступны',
text: 'Вы не авторизованы. Для личных сообщений сначала войдите в систему.',
});
return;
}
if (pageId === 'profile-view') {
openAuthRequiredModal({
title: 'Профиль недоступен',
text: 'Вы не авторизованы. Для профиля сначала войдите в систему.',
});
return;
}
if (pageId === 'notifications-view') {
openAuthRequiredModal({
title: 'Уведомления недоступны',
text: 'Вы не авторизованы. Для уведомлений сначала войдите в систему.',
});
return;
}
navigate(pageId);
}
export function renderToolbar(currentPageId, navigate) {
const root = document.createElement('nav');
root.className = 'toolbar';
const active = resolveToolbarActive(currentPageId);
ensureCountersPushBound();
const counters = normalizeCounters(state.userCounters);
ITEMS.forEach((item) => {
const btn = document.createElement('button');
const isProfile = item.pageId === 'profile-view';
const isMessages = item.pageId === 'messages-list';
const isNetwork = item.pageId === 'network-view';
const isNotifications = item.pageId === 'notifications-view';
btn.type = 'button';
if (item.pageId === active) btn.setAttribute('aria-current', 'page');
btn.dataset.toolbarPage = item.pageId;
btn.className = `toolbar-btn${item.pageId === active ? ' active' : ''}${isProfile ? ' toolbar-btn-profile' : ''}${isMessages ? ' toolbar-btn-messages' : ''}${isNetwork ? ' toolbar-btn-network' : ''}${item.hero ? ' toolbar-btn-hero' : ''}`;
if (isProfile) {
btn.innerHTML = `
${iconHtml(item)}
<span class="toolbar-label-wrap">
<span>${item.label}</span>
<span id="toolbar-connection-indicator" class="toolbar-connection-indicator is-unknown">
<span class="toolbar-connection-dot" aria-hidden="true"></span>
</span>
</span>
`;
} else if (isNetwork) {
btn.innerHTML = `${iconHtml(item)}<span>${item.label}</span>`;
btn.setAttribute('aria-label', item.label);
btn.title = item.label;
} else {
btn.innerHTML = `${iconHtml(item)}<span>${item.label}</span>`;
}
if (isMessages) renderBadge(btn, counters.dmUnreadCount, 'Непрочитанных личных сообщений');
if (item.pageId === 'channels-list') renderBadge(btn, counters.channelsUnreadCount, 'Непрочитанных сообщений в каналах');
if (isNotifications) renderBadge(btn, counters.notificationsUnreadCount, 'Новых уведомлений', 'notification-toolbar-badge');
if (item.pageId === 'channels-list') {
btn.addEventListener('click', () => navigate('channels-list'));
} else {
btn.addEventListener('click', () => navigateWithGuestRules(item.pageId, navigate));
}
root.append(btn);
});
if (state.session.isAuthorized) void refreshUserCounters();
return root;
}