From 801a4697ea035ccd857888bba599ef6b110352c042ab2228d7dc1af2efafab96 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 11 Aug 2026 15:22:10 +0400 Subject: [PATCH] =?UTF-8?q?UI:=20=D0=BE=D0=B1=D1=89=D0=B8=D0=B9=20=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=85=D0=BD=D0=B8=D0=B9=20=D0=B8=20=D0=BD=D0=B8?= =?UTF-8?q?=D0=B6=D0=BD=D0=B8=D0=B9=20=D1=82=D1=83=D0=BB=D0=B1=D0=B0=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 2 +- shine-UI/index.html | 2 + shine-UI/js/app.js | 102 ++++++++++++++++++++++- shine-UI/js/pages/channel-thread-view.js | 5 +- shine-UI/js/pages/channel-view.js | 4 +- shine-UI/js/pages/channels-list.js | 5 +- shine-UI/js/pages/chat-view.js | 9 +- shine-UI/js/pages/messages-list.js | 5 +- shine-UI/js/pages/profile-edit-view.js | 4 +- shine-UI/js/pages/profile-view.js | 36 ++++---- shine-UI/styles/components.css | 33 +++++++- shine-UI/styles/layout.css | 47 ++++++++++- 12 files changed, 218 insertions(+), 36 deletions(-) diff --git a/VERSION.properties b/VERSION.properties index f83edfa4..3c67cf67 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.5.31 +client.version=1.5.32 server.version=1.4.10 diff --git a/shine-UI/index.html b/shine-UI/index.html index a392a36e..fea3b7e3 100644 --- a/shine-UI/index.html +++ b/shine-UI/index.html @@ -40,7 +40,9 @@ window.__SHINE_BUILD_HASH__ = '20260806223040';
Сияние
+
+
diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index a8d07478..50789928 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -151,6 +151,8 @@ const routes = { }; const screenEl = document.getElementById('app-screen'); +const topbarEl = document.getElementById('topbar-slot'); +const composerEl = document.getElementById('composer-slot'); const toolbarEl = document.getElementById('toolbar-slot'); const appShellEl = document.querySelector('.app-shell'); const initialSplashEl = document.getElementById('initial-splash'); @@ -181,6 +183,7 @@ let hiddenDmAudioContext = null; let hiddenDmAudioUnlocked = false; let initialConnectionCompleted = false; let orientationLockInFlight = false; +let currentChromeCleanup = null; const CALL_PUSH_PENDING_ACTION_KEY = 'shine-ui-call-push-pending-action-v1'; const GUEST_ALLOWED_PAGES = new Set([ 'start-view', @@ -204,6 +207,86 @@ initPwaInstallPromptHandling(); initCallUiOverlay(); setCallDebugReporter((payload) => authService.reportClientDebug(payload)); +function setShellMetricVar(name, valuePx) { + if (!appShellEl) return; + appShellEl.style.setProperty(name, `${Math.max(0, Math.ceil(Number(valuePx || 0)))}px`); +} + +function attachSlotHeightObserver(slotEl, cssVarName) { + if (!slotEl || typeof ResizeObserver !== 'function') return null; + const sync = () => { + const visible = !slotEl.hidden && slotEl.childElementCount > 0; + setShellMetricVar(cssVarName, visible ? slotEl.offsetHeight : 0); + }; + const observer = new ResizeObserver(sync); + observer.observe(slotEl); + sync(); + return { observer, sync }; +} + +const topbarHeightObserver = attachSlotHeightObserver(topbarEl, '--topbar-height'); +const composerHeightObserver = attachSlotHeightObserver(composerEl, '--composer-height'); +const toolbarHeightObserver = attachSlotHeightObserver(toolbarEl, '--toolbar-height'); + +function clearSlot(slotEl, cssVarName) { + if (!slotEl) return; + slotEl.innerHTML = ''; + slotEl.hidden = true; + setShellMetricVar(cssVarName, 0); +} + +function mountSlot(slotEl, cssVarName, node) { + if (!slotEl) return; + slotEl.innerHTML = ''; + if (node instanceof Node) { + slotEl.append(node); + slotEl.hidden = false; + } else { + slotEl.hidden = true; + } + setShellMetricVar(cssVarName, !slotEl.hidden ? slotEl.offsetHeight : 0); +} + +function createChromeController(showAppChrome) { + let topbarNode = null; + let composerNode = null; + let disposed = false; + + const apply = () => { + if (disposed) return; + if (!showAppChrome) { + clearSlot(topbarEl, '--topbar-height'); + clearSlot(composerEl, '--composer-height'); + return; + } + mountSlot(topbarEl, '--topbar-height', topbarNode); + mountSlot(composerEl, '--composer-height', composerNode); + topbarHeightObserver?.sync?.(); + composerHeightObserver?.sync?.(); + }; + + return { + setTopbar(node = null) { + topbarNode = node instanceof Node ? node : null; + apply(); + }, + setComposer(node = null) { + composerNode = node instanceof Node ? node : null; + apply(); + }, + clear() { + topbarNode = null; + composerNode = null; + apply(); + }, + dispose() { + disposed = true; + clearSlot(topbarEl, '--topbar-height'); + clearSlot(composerEl, '--composer-height'); + }, + }; +} + async function unlockHiddenDmAudio() { try { const Ctx = window.AudioContext || window.webkitAudioContext; @@ -908,7 +991,14 @@ function renderPageFailureFallback(pageId, error) { screenEl.append(wrap); screenEl.classList.toggle('no-app-chrome', false); + if (typeof currentChromeCleanup === 'function') { + currentChromeCleanup(); + currentChromeCleanup = null; + } + clearSlot(topbarEl, '--topbar-height'); + clearSlot(composerEl, '--composer-height'); toolbarEl.innerHTML = ''; + toolbarHeightObserver?.sync?.(); refreshConnectionUi(); } @@ -932,10 +1022,17 @@ function renderApp() { currentCleanup(); currentCleanup = null; } + if (typeof currentChromeCleanup === 'function') { + currentChromeCleanup(); + currentChromeCleanup = null; + } try { screenEl.innerHTML = ''; - const screen = page.render({ route, navigate }); + const showAppChrome = page.pageMeta?.showAppChrome !== false; + const chrome = createChromeController(showAppChrome); + currentChromeCleanup = () => chrome.dispose(); + const screen = page.render({ route, navigate, chrome }); if (!(screen instanceof Node)) { throw new Error('Page render returned invalid node'); } @@ -943,7 +1040,6 @@ function renderApp() { screenEl.append(screen); currentCleanup = typeof screen.cleanup === 'function' ? screen.cleanup : null; - const showAppChrome = page.pageMeta?.showAppChrome !== false; screenEl.classList.toggle('no-app-chrome', !showAppChrome); screenEl.classList.toggle('preauth-flow', PRE_AUTH_PAGES.includes(pageId)); @@ -951,6 +1047,7 @@ function renderApp() { if (showAppChrome) { toolbarEl.append(renderToolbar(page.pageMeta.id, navigate)); } + toolbarHeightObserver?.sync?.(); refreshConnectionUi(); } catch (error) { console.error('[renderApp] controlled fallback', error); @@ -968,6 +1065,7 @@ function refreshToolbarOnly() { if (showAppChrome) { toolbarEl.append(renderToolbar(page.pageMeta.id, navigate)); } + toolbarHeightObserver?.sync?.(); refreshConnectionUi(); } diff --git a/shine-UI/js/pages/channel-thread-view.js b/shine-UI/js/pages/channel-thread-view.js index cefcea25..8d68578d 100644 --- a/shine-UI/js/pages/channel-thread-view.js +++ b/shine-UI/js/pages/channel-thread-view.js @@ -1114,7 +1114,7 @@ function renderSkeleton(screen) { return wrap; } -export function render({ navigate, route }) { +export function render({ navigate, route, chrome }) { const selector = parseThreadSelector(route); const channelDisplayName = resolveChannelDisplayName(selector?.channel); const routeKey = `${selector?.message?.blockchainName || ''}:${selector?.message?.blockNumber || ''}:${selector?.message?.blockHash || ''}`; @@ -1134,6 +1134,7 @@ export function render({ navigate, route }) { threadHeaderButton.classList.add('channel-header-route-btn'); threadHeaderButton.disabled = true; } + chrome?.setTopbar(header); const statusBox = document.createElement('div'); statusBox.className = 'card status-line is-unavailable channels-status'; @@ -1309,7 +1310,7 @@ export function render({ navigate, route }) { }, }; - screen.append(header, statusBox); + screen.append(statusBox); if (!selector) { const invalid = document.createElement('div'); diff --git a/shine-UI/js/pages/channel-view.js b/shine-UI/js/pages/channel-view.js index c4bd8ef7..699c441b 100644 --- a/shine-UI/js/pages/channel-view.js +++ b/shine-UI/js/pages/channel-view.js @@ -2008,7 +2008,7 @@ function renderSkeleton(screen) { return wrap; } -export function render({ navigate, route }) { +export function render({ navigate, route, chrome }) { const channelId = route.params.channelId || ''; const routeSelector = buildSelectorFromRoute(route, channelId); const routeKey = `${routeSelector?.ownerBlockchainName || ''}:${routeSelector?.channelRootBlockNumber || ''}:${routeSelector?.channelRootBlockHash || ''}`; @@ -2049,6 +2049,7 @@ export function render({ navigate, route }) { channelEntrypointButton.disabled = true; channelEntrypointButton.hidden = true; } + chrome?.setTopbar(header); const rerender = () => { const current = document.querySelector('section.channels-screen--channel'); @@ -2282,7 +2283,6 @@ export function render({ navigate, route }) { rerender(); }; - screen.append(header); screen.append(statusBox); const skeleton = renderSkeleton(screen); diff --git a/shine-UI/js/pages/channels-list.js b/shine-UI/js/pages/channels-list.js index 93a50226..49ab2531 100644 --- a/shine-UI/js/pages/channels-list.js +++ b/shine-UI/js/pages/channels-list.js @@ -1213,7 +1213,7 @@ async function loadFeedAndRender({ screen, listState, contentEl, navigate }) { } } -export function render({ navigate, route }) { +export function render({ navigate, route, chrome }) { const screen = document.createElement('section'); screen.className = 'stack channels-screen channels-screen--list'; const appScreen = document.getElementById('app-screen'); @@ -1310,7 +1310,8 @@ export function render({ navigate, route }) { updateBottomCta({ button: bottomCta }); }; - screen.append(topBarEl, contentEl, bottomCta); + chrome?.setTopbar(topBarEl); + screen.append(contentEl, bottomCta); if (createSuccessFlash) { showToast(createSuccessFlash); diff --git a/shine-UI/js/pages/chat-view.js b/shine-UI/js/pages/chat-view.js index 9962ce1d..c6d921ea 100644 --- a/shine-UI/js/pages/chat-view.js +++ b/shine-UI/js/pages/chat-view.js @@ -785,7 +785,7 @@ function setChatKeyboardOpen(isOpen) { document.body.classList.toggle('chat-keyboard-open', !!isOpen); } -export function render({ navigate, route }) { +export function render({ navigate, route, chrome }) { const routeChatId = route.params.chatId || 'u1'; const chatId = normalizeDmChatId(routeChatId) || 'u1'; const contact = directMessages.find((d) => normalizeDmChatId(d.id) === chatId) || { @@ -871,7 +871,7 @@ export function render({ navigate, route }) { const log = document.createElement('div'); log.className = 'messages-log dm-messages-log'; - screen.append( + chrome?.setTopbar( renderHeader({ title: `Чат с ${contact.name}`, leftAction: { label: '←', onClick: () => navigate('messages-list') }, @@ -941,7 +941,7 @@ export function render({ navigate, route }) { }, }, ], - }) + }), ); if (!isKnownContact) { @@ -1490,7 +1490,8 @@ export function render({ navigate, route }) { window.visualViewport?.addEventListener('resize', syncKeyboardUi); window.addEventListener('resize', syncKeyboardUi); - wrap.append(historyLoader, log, form); + chrome?.setComposer(form); + wrap.append(historyLoader, log); screen.append(wrap); renderLog(log, chatId, { onOpenActions: handleOpenActions, diff --git a/shine-UI/js/pages/messages-list.js b/shine-UI/js/pages/messages-list.js index 620d7ef3..4bd24af5 100644 --- a/shine-UI/js/pages/messages-list.js +++ b/shine-UI/js/pages/messages-list.js @@ -84,7 +84,7 @@ function formatChatRowTime(ts) { const SVG_CHEVRON = ''; -export function render({ navigate }) { +export function render({ navigate, chrome }) { const screen = document.createElement('section'); screen.className = 'stack dm-screen dm-list-screen'; const login = String(state.session.login || '').trim(); @@ -248,7 +248,8 @@ export function render({ navigate }) { } } - screen.append(head, divider, list); + chrome?.setTopbar(head); + screen.append(divider, list); loadList(); return screen; } diff --git a/shine-UI/js/pages/profile-edit-view.js b/shine-UI/js/pages/profile-edit-view.js index b86ac20c..fad1994e 100644 --- a/shine-UI/js/pages/profile-edit-view.js +++ b/shine-UI/js/pages/profile-edit-view.js @@ -87,13 +87,13 @@ function escapeHtml(text) { .replaceAll("'", '''); } -export function render({ navigate }) { +export function render({ navigate, chrome }) { const login = state.session.login || profile.login; const screen = document.createElement('section'); screen.className = 'stack profile-screen'; - screen.append( + chrome?.setTopbar( renderHeader({ title: 'Редактирование профиля', leftAction: { label: '←', onClick: () => navigate('profile-view') }, diff --git a/shine-UI/js/pages/profile-view.js b/shine-UI/js/pages/profile-view.js index 4682f5bf..16661c25 100644 --- a/shine-UI/js/pages/profile-view.js +++ b/shine-UI/js/pages/profile-view.js @@ -92,28 +92,34 @@ function shineInfoText() { + '5) сияющие заботятся о мире: о людях, гармонии и общем благе.'; } -export function render({ navigate }) { +export function render({ navigate, chrome }) { const login = state.session.login || profile.login; const screen = document.createElement('section'); screen.className = 'stack profile-screen'; - const topActions = document.createElement('div'); - topActions.className = 'profile-top-actions'; - topActions.innerHTML = ` - - - + const topbar = document.createElement('header'); + topbar.className = 'page-header app-topbar-shell app-topbar-shell--profile'; + topbar.innerHTML = ` +
+

Профиль пользователя

+
+ + + +
`; + const topActions = topbar.querySelector('.profile-top-actions'); topActions.querySelector('[data-top-action="profile"]')?.addEventListener('click', () => navigate('profile-edit-view')); - topActions.querySelector('[data-top-action="wallet"]')?.addEventListener('click', () => navigate('wallet')); + topActions.querySelector('[data-top-action="wallet"]')?.addEventListener('click', () => navigate('wallet-view')); topActions.querySelector('[data-top-action="settings"]')?.addEventListener('click', () => navigate('settings-view')); + chrome?.setTopbar(topbar); const card = document.createElement('div'); card.className = 'card stack profile-main-card'; @@ -264,7 +270,7 @@ export function render({ navigate }) { } } - card.append(topActions, topRow, badgesRow, listWrap); + card.append(topRow, badgesRow, listWrap); screen.append(card); updateAvatarUi(); diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 8f18cbad..80391fa9 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -13,6 +13,37 @@ padding-top: max(0px, env(safe-area-inset-top)); } +.topbar-slot .page-header, +.topbar-slot .dm-head, +.topbar-slot .channels-top-bar { + margin-bottom: 0; + border-radius: 18px; + background: rgba(14, 21, 35, 0.92); + border: 1px solid rgba(212, 175, 55, 0.18); + box-shadow: 0 10px 26px rgba(4, 8, 16, 0.28); + backdrop-filter: blur(14px); +} + +.topbar-slot .page-header { + padding: calc(10px + env(safe-area-inset-top)) 12px 10px; +} + +.topbar-slot .dm-head, +.topbar-slot .channels-top-bar { + padding-top: calc(10px + env(safe-area-inset-top)); + padding-bottom: 10px; +} + +.app-topbar-shell .page-title { + flex: 1 1 auto; + text-align: center; +} + +.app-topbar-shell .header-left, +.app-topbar-shell .header-actions { + min-width: 92px; +} + .page-title { font-size: 22px; font-weight: 700; @@ -1561,7 +1592,7 @@ } .toolbar-btn-profile .toolbar-label-wrap { - padding-bottom: 6px; + padding-bottom: 0; } .toolbar-btn-profile .toolbar-connection-indicator { diff --git a/shine-UI/styles/layout.css b/shine-UI/styles/layout.css index 34448a7c..77257890 100644 --- a/shine-UI/styles/layout.css +++ b/shine-UI/styles/layout.css @@ -25,6 +25,9 @@ body::before { height: 100dvh; position: relative; --call-minimized-bar-height: 0px; + --topbar-height: 0px; + --composer-height: 0px; + --toolbar-height: 78px; background: transparent; border-left: 1px solid rgba(211, 170, 86, 0.2); border-right: 1px solid rgba(211, 170, 86, 0.2); @@ -33,6 +36,8 @@ body::before { } .screen-content, +.topbar-slot, +.composer-slot, .toolbar-slot, .connection-retry-banner { z-index: 1; @@ -40,15 +45,16 @@ body::before { .screen-content { position: absolute; - top: var(--call-minimized-bar-height, 0px); + top: calc(var(--call-minimized-bar-height, 0px) + var(--topbar-height, 0px)); left: 0; right: 0; - bottom: 62px; + bottom: calc(var(--toolbar-height, 78px) + var(--composer-height, 0px)); overflow-y: auto; padding: 14px 14px 24px; } .screen-content.no-app-chrome { + top: var(--call-minimized-bar-height, 0px); bottom: 0; padding-bottom: calc(24px + env(safe-area-inset-bottom)); } @@ -62,6 +68,36 @@ body::before { padding: 0; } +.topbar-slot[hidden], +.composer-slot[hidden] { + display: none; +} + +.topbar-slot { + position: absolute; + top: var(--call-minimized-bar-height, 0px); + left: 0; + right: 0; + padding: 0 12px; + pointer-events: none; +} + +.topbar-slot > * { + pointer-events: auto; +} + +.composer-slot { + position: absolute; + left: 0; + right: 0; + bottom: calc(var(--toolbar-height, 78px) + env(safe-area-inset-bottom)); + padding: 0 12px 8px; + pointer-events: none; +} + +.composer-slot > * { + pointer-events: auto; +} .toolbar-slot { position: absolute; @@ -74,10 +110,15 @@ body::before { } body.chat-keyboard-open .screen-content { - bottom: 0; + bottom: var(--composer-height, 0px); padding-bottom: calc(14px + env(safe-area-inset-bottom)); } +body.chat-keyboard-open .composer-slot { + bottom: 0; + padding-bottom: calc(8px + env(safe-area-inset-bottom)); +} + body.chat-keyboard-open .toolbar-slot { opacity: 0; pointer-events: none;