Compare commits

...
2 Commits
Author SHA256 Message Date
tram f0b8fac598 Merge origin/main into ui-artem 2026-09-01 12:39:02 +03:00
tram 3ae9d9175f Обновить UI-ветку 2026-09-01 12:32:50 +03:00
4 changed files with 114 additions and 7 deletions
+2
View File
@@ -1,3 +1,5 @@
import org.bouncycastle.util.Properties
plugins { plugins {
id 'java' id 'java'
id 'application' id 'application'
+13 -4
View File
@@ -952,6 +952,9 @@ function closeTopChannelsMenu(listState) {
listState.topMenuCleanup(); listState.topMenuCleanup();
} }
listState.topMenuCleanup = null; listState.topMenuCleanup = null;
listState.topMenuAnchor?.setAttribute('aria-expanded', 'false');
listState.topMenuAnchor?.classList.remove('menu-open-pressed');
listState.topMenuAnchor = null;
const root = document.getElementById('modal-root'); const root = document.getElementById('modal-root');
if (root) { if (root) {
const overlay = root.querySelector(`#${TOP_MENU_OVERLAY_ID}`); const overlay = root.querySelector(`#${TOP_MENU_OVERLAY_ID}`);
@@ -1002,9 +1005,6 @@ function openTopChannelsMenu({
if (item.divider) { if (item.divider) {
const divider = document.createElement('div'); const divider = document.createElement('div');
divider.className = 'channel-menu-divider'; divider.className = 'channel-menu-divider';
divider.style.height = '1px';
divider.style.background = 'rgba(255,255,255,0.08)';
divider.style.margin = '6px 0';
menu.append(divider); menu.append(divider);
return; return;
} }
@@ -1021,6 +1021,9 @@ function openTopChannelsMenu({
overlay.append(menu); overlay.append(menu);
root.append(overlay); root.append(overlay);
anchorEl.setAttribute('aria-expanded', 'true');
anchorEl.classList.add('menu-open-pressed');
listState.topMenuAnchor = anchorEl;
const onOverlayClick = (event) => { const onOverlayClick = (event) => {
if (event.target === overlay) closeTopChannelsMenu(listState); if (event.target === overlay) closeTopChannelsMenu(listState);
@@ -1354,6 +1357,7 @@ export function render({ navigate, route, chrome }) {
const listState = { const listState = {
openMenuId: null, openMenuId: null,
topMenuCleanup: null, topMenuCleanup: null,
topMenuAnchor: null,
notificationsState, notificationsState,
revealedCounters: new Set(), revealedCounters: new Set(),
channels: [], channels: [],
@@ -1393,11 +1397,16 @@ export function render({ navigate, route, chrome }) {
topMenuBtn.type = 'button'; topMenuBtn.type = 'button';
topMenuBtn.className = 'icon-btn channels-top-more-btn'; topMenuBtn.className = 'icon-btn channels-top-more-btn';
topMenuBtn.setAttribute('aria-label', 'Ещё действия'); topMenuBtn.setAttribute('aria-label', 'Ещё действия');
topMenuBtn.setAttribute('aria-haspopup', 'menu');
topMenuBtn.setAttribute('aria-expanded', 'false');
topMenuBtn.title = 'Ещё действия'; topMenuBtn.title = 'Ещё действия';
topMenuBtn.append(createOverflowDots()); topMenuBtn.append(createOverflowDots());
topMenuBtn.addEventListener('click', (event) => { topMenuBtn.addEventListener('click', (event) => {
event.stopPropagation(); event.stopPropagation();
animatePress(topMenuBtn); if (listState.topMenuAnchor === topMenuBtn) {
closeTopChannelsMenu(listState);
return;
}
openTopChannelsMenu({ openTopChannelsMenu({
listState, listState,
anchorEl: topMenuBtn, anchorEl: topMenuBtn,
+97 -1
View File
@@ -1,4 +1,5 @@
import { renderHeader } from '../components/header.js'; import { renderHeader } from '../components/header.js';
import { createOverflowDots } from '../components/overflow-dots.js';
import { authService, state } from '../state.js'; import { authService, state } from '../state.js';
import { makeProfileRoute } from '../services/shine-routes.js'; import { makeProfileRoute } from '../services/shine-routes.js';
import { makeProfileLinksRoute } from '../services/shine-routes.js'; import { makeProfileLinksRoute } from '../services/shine-routes.js';
@@ -457,18 +458,113 @@ export function render({ navigate, route, chrome } = {}) {
} }
} }
let topMenuPortal = null;
let topMenuBackdrop = null;
let topMenuButton = null;
const closeTopMenu = () => {
topMenuPortal?.remove();
topMenuBackdrop?.remove();
topMenuPortal = null;
topMenuBackdrop = null;
topMenuButton?.setAttribute('aria-expanded', 'false');
topMenuButton?.classList.remove('menu-open-pressed');
};
const positionTopMenu = () => {
if (!topMenuPortal || !topMenuButton) return;
const rect = topMenuButton.getBoundingClientRect();
const margin = 10;
const menuRect = topMenuPortal.getBoundingClientRect();
const menuWidth = menuRect.width || topMenuPortal.offsetWidth || 190;
const menuHeight = menuRect.height || topMenuPortal.offsetHeight || 56;
const left = Math.max(margin, Math.min(window.innerWidth - menuWidth - margin, rect.right - menuWidth));
const below = rect.bottom + 7;
const top = below + menuHeight <= window.innerHeight - margin
? below
: Math.max(margin, rect.top - menuHeight - 7);
topMenuPortal.style.left = `${Math.round(left)}px`;
topMenuPortal.style.top = `${Math.round(top)}px`;
};
const openTopMenu = () => {
if (!topMenuButton || topMenuPortal) return;
const portal = document.createElement('div');
portal.className = 'dm-head-menu dm-head-menu--portal network-head-menu';
portal.setAttribute('role', 'menu');
portal.innerHTML = `
<button type="button" class="dm-head-menu-item network-head-menu-item" role="menuitem" data-action="find-person">
<span>Найти человека</span>
</button>
`;
portal.querySelector('[data-action="find-person"]')?.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
closeTopMenu();
openSearchModal();
});
portal.addEventListener('click', (event) => event.stopPropagation());
const backdrop = document.createElement('div');
backdrop.className = 'ui-menu-dim-layer';
backdrop.addEventListener('click', closeTopMenu);
document.body.append(backdrop, portal);
topMenuBackdrop = backdrop;
topMenuPortal = portal;
topMenuButton.setAttribute('aria-expanded', 'true');
topMenuButton.classList.add('menu-open-pressed');
positionTopMenu();
requestAnimationFrame(positionTopMenu);
};
const toggleTopMenu = (event) => {
event?.preventDefault?.();
event?.stopPropagation?.();
if (topMenuPortal) closeTopMenu();
else openTopMenu();
};
const header = renderHeader({ const header = renderHeader({
title: 'Связи', title: 'Связи',
rightActions: [ rightActions: [
{ label: 'Найти', onClick: openSearchModal }, {
iconNode: createOverflowDots(),
className: 'network-top-more-btn',
title: 'Ещё действия',
ariaLabel: 'Меню связей',
onClick: toggleTopMenu,
},
], ],
}); });
// «Связи» используют тот же общий topbar, что и остальные страницы. // «Связи» используют тот же общий topbar, что и остальные страницы.
// Отдельный класс нужен только для page-specific fade графа, не для геометрии header. // Отдельный класс нужен только для page-specific fade графа, не для геометрии header.
header.classList.add('network-topbar'); header.classList.add('network-topbar');
topMenuButton = header.querySelector('.network-top-more-btn');
topMenuButton?.setAttribute('aria-haspopup', 'menu');
topMenuButton?.setAttribute('aria-expanded', 'false');
const onTopMenuOutsideClick = (event) => {
if (!topMenuPortal) return;
if (topMenuPortal.contains(event.target) || topMenuButton?.contains(event.target)) return;
closeTopMenu();
};
const onTopMenuKeydown = (event) => {
if (event.key !== 'Escape' || !topMenuPortal) return;
closeTopMenu();
topMenuButton?.focus();
};
const onTopMenuViewportChange = () => positionTopMenu();
document.addEventListener('click', onTopMenuOutsideClick);
document.addEventListener('keydown', onTopMenuKeydown);
window.addEventListener('resize', onTopMenuViewportChange, { passive: true });
window.addEventListener('scroll', onTopMenuViewportChange, { passive: true, capture: true });
// Ресайз и перерисовку рёбер движок обрабатывает сам (window resize + ResizeObserver внутри). // Ресайз и перерисовку рёбер движок обрабатывает сам (window resize + ResizeObserver внутри).
screen.cleanup = () => { screen.cleanup = () => {
closeTopMenu();
document.removeEventListener('click', onTopMenuOutsideClick);
document.removeEventListener('keydown', onTopMenuKeydown);
window.removeEventListener('resize', onTopMenuViewportChange);
window.removeEventListener('scroll', onTopMenuViewportChange, true);
if (engine) engine.destroy(); if (engine) engine.destroy();
engine = null; engine = null;
appScreenEl?.classList.remove('network-scroll-lock'); appScreenEl?.classList.remove('network-scroll-lock');
+2 -2
View File
@@ -34,8 +34,8 @@ body::before {
--toolbar-height: 78px; --toolbar-height: 78px;
--keyboard-offset: 0px; --keyboard-offset: 0px;
background: transparent; background: transparent;
border-left: 1px solid rgba(211, 170, 86, 0.2); border-left: 0;
border-right: 1px solid rgba(211, 170, 86, 0.2); border-right: 0;
box-shadow: var(--shadow); box-shadow: var(--shadow);
overflow: hidden; overflow: hidden;
} }