159 lines
5.1 KiB
JavaScript
159 lines
5.1 KiB
JavaScript
import { resolveToolbarActive } from '../router.js';
|
|
import { state } from '../state.js';
|
|
|
|
const ITEMS = [
|
|
{ pageId: 'messages-list', label: 'Личные сообщения', icon: '💬' },
|
|
{ pageId: 'channels-list', label: 'Каналы', icon: '📢' },
|
|
{ pageId: 'network-view', label: 'Связи', icon: '🕸' },
|
|
{ pageId: 'notifications-view', label: 'Уведомления', icon: '🔔' },
|
|
{ pageId: 'profile-view', label: 'Профиль', icon: '👤' },
|
|
];
|
|
const CHANNEL_HOLD_MS = 260;
|
|
const CHANNEL_MODES = Object.freeze([
|
|
{ key: 'feed', label: 'Каналы' },
|
|
{ key: 'dialogs', label: 'Чаты' },
|
|
{ key: 'my', label: 'Мои' },
|
|
]);
|
|
|
|
function getTotalUnreadMessages() {
|
|
const chats = Object.values(state.chats || {});
|
|
let total = 0;
|
|
chats.forEach((messages) => {
|
|
if (!Array.isArray(messages)) return;
|
|
messages.forEach((msg) => {
|
|
if (msg?.from === 'in' && msg?.unread) total += 1;
|
|
});
|
|
});
|
|
return total;
|
|
}
|
|
|
|
export function renderToolbar(currentPageId, navigate) {
|
|
const root = document.createElement('nav');
|
|
root.className = 'toolbar';
|
|
const active = resolveToolbarActive(currentPageId);
|
|
const unreadTotal = getTotalUnreadMessages();
|
|
|
|
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';
|
|
btn.className = `toolbar-btn${item.pageId === active ? ' active' : ''}${isProfile ? ' toolbar-btn-profile' : ''}${isMessages ? ' toolbar-btn-messages' : ''}${isNetwork ? ' toolbar-btn-network' : ''}`;
|
|
if (isProfile) {
|
|
btn.innerHTML = `
|
|
<span>${item.icon}</span>
|
|
<span class="toolbar-label-wrap">
|
|
<span>${item.label}</span>
|
|
<span id="toolbar-connection-indicator" class="toolbar-connection-indicator is-unknown">
|
|
<span class="toolbar-connection-text">connected</span>
|
|
<span class="toolbar-connection-dot" aria-hidden="true"></span>
|
|
</span>
|
|
</span>
|
|
`;
|
|
} else {
|
|
btn.innerHTML = `<span>${item.icon}</span><span>${item.label}</span>`;
|
|
}
|
|
if (isMessages && unreadTotal > 0) {
|
|
const badge = document.createElement('span');
|
|
badge.className = 'toolbar-unread-badge';
|
|
badge.textContent = unreadTotal > 99 ? '99+' : String(unreadTotal);
|
|
badge.setAttribute('aria-label', `Непрочитанных сообщений: ${badge.textContent}`);
|
|
btn.append(badge);
|
|
}
|
|
if (item.pageId === 'channels-list') {
|
|
installChannelsHoldSwitcher(btn, navigate);
|
|
} else {
|
|
btn.addEventListener('click', () => navigate(item.pageId));
|
|
}
|
|
root.append(btn);
|
|
});
|
|
|
|
return root;
|
|
}
|
|
|
|
function installChannelsHoldSwitcher(button, navigate) {
|
|
let holdTimer = 0;
|
|
let pressed = false;
|
|
let holdActive = false;
|
|
let overlay = null;
|
|
let selectedMode = 'dialogs';
|
|
|
|
const clearTimer = () => {
|
|
if (holdTimer) {
|
|
window.clearTimeout(holdTimer);
|
|
holdTimer = 0;
|
|
}
|
|
};
|
|
|
|
const closeOverlay = () => {
|
|
if (overlay) overlay.remove();
|
|
overlay = null;
|
|
holdActive = false;
|
|
};
|
|
|
|
const setSelectedModeByX = (clientX) => {
|
|
if (!overlay) return;
|
|
const rect = overlay.getBoundingClientRect();
|
|
const part = rect.width / 3;
|
|
const localX = Math.max(0, Math.min(rect.width - 1, clientX - rect.left));
|
|
const index = Math.max(0, Math.min(2, Math.floor(localX / Math.max(1, part))));
|
|
selectedMode = CHANNEL_MODES[index].key;
|
|
const buttons = overlay.querySelectorAll('.toolbar-channels-hold-item');
|
|
buttons.forEach((el, idx) => {
|
|
el.classList.toggle('is-active', idx === index);
|
|
});
|
|
};
|
|
|
|
const openOverlay = () => {
|
|
const rect = button.getBoundingClientRect();
|
|
overlay = document.createElement('div');
|
|
overlay.className = 'toolbar-channels-hold-overlay';
|
|
overlay.innerHTML = CHANNEL_MODES.map((mode) => (
|
|
`<button type="button" class="toolbar-channels-hold-item${mode.key === selectedMode ? ' is-active' : ''}" data-mode="${mode.key}">${mode.label}</button>`
|
|
)).join('');
|
|
overlay.style.left = `${Math.round(rect.left + rect.width / 2)}px`;
|
|
overlay.style.top = `${Math.round(rect.top - 12)}px`;
|
|
document.body.append(overlay);
|
|
holdActive = true;
|
|
};
|
|
|
|
button.addEventListener('pointerdown', (event) => {
|
|
pressed = true;
|
|
holdActive = false;
|
|
selectedMode = 'dialogs';
|
|
clearTimer();
|
|
holdTimer = window.setTimeout(() => {
|
|
if (!pressed) return;
|
|
openOverlay();
|
|
setSelectedModeByX(event.clientX);
|
|
}, CHANNEL_HOLD_MS);
|
|
});
|
|
|
|
button.addEventListener('pointermove', (event) => {
|
|
if (holdActive) setSelectedModeByX(event.clientX);
|
|
});
|
|
|
|
button.addEventListener('pointerup', () => {
|
|
clearTimer();
|
|
const wasHold = holdActive;
|
|
const mode = selectedMode;
|
|
pressed = false;
|
|
closeOverlay();
|
|
if (wasHold) {
|
|
navigate(`channels-list/${mode}`);
|
|
return;
|
|
}
|
|
navigate('channels-list/dialogs');
|
|
});
|
|
|
|
button.addEventListener('pointercancel', () => {
|
|
clearTimer();
|
|
pressed = false;
|
|
closeOverlay();
|
|
});
|
|
|
|
button.addEventListener('contextmenu', (event) => {
|
|
event.preventDefault();
|
|
});
|
|
}
|