SHA256
Обновить UI профиля и навигации
This commit is contained in:
+140
-32
@@ -399,14 +399,88 @@ if (new URLSearchParams(window.location.search).get('keyboard-debug') === '1') {
|
||||
syncDebug();
|
||||
}
|
||||
|
||||
function clearSlot(slotEl, cssVarName) {
|
||||
const MANAGED_SHELL_CLASSES = [
|
||||
'app-shell--top-fade',
|
||||
'app-shell--bottom-fade',
|
||||
'app-shell--bottom-fade-composer',
|
||||
'app-shell--bottom-fade-toolbar',
|
||||
'app-shell--fade-edge',
|
||||
'app-shell--content-under-topbar',
|
||||
'app-shell--content-under-bottom',
|
||||
'app-shell--scroll-nested',
|
||||
'app-shell--scroll-locked',
|
||||
'app-shell--scrollbar-hidden',
|
||||
];
|
||||
|
||||
const MANAGED_SCREEN_CLASSES = [
|
||||
'no-app-chrome',
|
||||
'preauth-flow',
|
||||
'filled-action-buttons',
|
||||
'settings-bordered-actions',
|
||||
];
|
||||
|
||||
const DEFAULT_SHELL_MODE = Object.freeze({
|
||||
topFade: true,
|
||||
bottomFade: false,
|
||||
bottomFadeAnchor: 'composer',
|
||||
fadeProfile: 'standard',
|
||||
contentUnderTopbar: true,
|
||||
contentUnderBottom: false,
|
||||
scrollContainer: 'screen',
|
||||
scrollbar: 'auto',
|
||||
});
|
||||
|
||||
function normalizeShellMode(mode = {}, showAppChrome = true) {
|
||||
const source = mode && typeof mode === 'object' ? mode : {};
|
||||
const normalized = { ...DEFAULT_SHELL_MODE, ...source };
|
||||
if (!showAppChrome) {
|
||||
normalized.topFade = false;
|
||||
normalized.bottomFade = false;
|
||||
normalized.contentUnderTopbar = false;
|
||||
normalized.contentUnderBottom = false;
|
||||
}
|
||||
normalized.bottomFadeAnchor = normalized.bottomFadeAnchor === 'toolbar' ? 'toolbar' : 'composer';
|
||||
normalized.fadeProfile = normalized.fadeProfile === 'edge' ? 'edge' : 'standard';
|
||||
normalized.scrollContainer = ['screen', 'nested', 'locked'].includes(normalized.scrollContainer)
|
||||
? normalized.scrollContainer
|
||||
: 'screen';
|
||||
normalized.scrollbar = normalized.scrollbar === 'hidden' ? 'hidden' : 'auto';
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function applyShellMode(mode, showAppChrome = true) {
|
||||
if (!appShellEl) return normalizeShellMode(mode, showAppChrome);
|
||||
const normalized = normalizeShellMode(mode, showAppChrome);
|
||||
appShellEl.classList.toggle('app-shell--top-fade', Boolean(normalized.topFade));
|
||||
appShellEl.classList.toggle('app-shell--bottom-fade', Boolean(normalized.bottomFade));
|
||||
appShellEl.classList.toggle('app-shell--bottom-fade-composer', Boolean(normalized.bottomFade) && normalized.bottomFadeAnchor === 'composer');
|
||||
appShellEl.classList.toggle('app-shell--bottom-fade-toolbar', Boolean(normalized.bottomFade) && normalized.bottomFadeAnchor === 'toolbar');
|
||||
appShellEl.classList.toggle('app-shell--fade-edge', normalized.fadeProfile === 'edge');
|
||||
appShellEl.classList.toggle('app-shell--content-under-topbar', Boolean(normalized.contentUnderTopbar));
|
||||
appShellEl.classList.toggle('app-shell--content-under-bottom', Boolean(normalized.contentUnderBottom));
|
||||
appShellEl.classList.toggle('app-shell--scroll-nested', normalized.scrollContainer === 'nested');
|
||||
appShellEl.classList.toggle('app-shell--scroll-locked', normalized.scrollContainer === 'locked');
|
||||
appShellEl.classList.toggle('app-shell--scrollbar-hidden', normalized.scrollbar === 'hidden');
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function resetShellMode() {
|
||||
appShellEl?.classList.remove(...MANAGED_SHELL_CLASSES);
|
||||
}
|
||||
|
||||
function resetManagedScreenClasses() {
|
||||
screenEl?.classList.remove(...MANAGED_SCREEN_CLASSES);
|
||||
}
|
||||
|
||||
function clearSlot(slotEl, cssVarName, presenceClass = '') {
|
||||
if (!slotEl) return;
|
||||
slotEl.innerHTML = '';
|
||||
slotEl.hidden = true;
|
||||
if (presenceClass) appShellEl?.classList.remove(presenceClass);
|
||||
setShellMetricVar(cssVarName, 0);
|
||||
}
|
||||
|
||||
function mountSlot(slotEl, cssVarName, node) {
|
||||
function mountSlot(slotEl, cssVarName, node, presenceClass = '') {
|
||||
if (!slotEl) return;
|
||||
slotEl.innerHTML = '';
|
||||
if (node instanceof Node) {
|
||||
@@ -415,59 +489,89 @@ function mountSlot(slotEl, cssVarName, node) {
|
||||
} else {
|
||||
slotEl.hidden = true;
|
||||
}
|
||||
if (presenceClass) appShellEl?.classList.toggle(presenceClass, !slotEl.hidden);
|
||||
setShellMetricVar(cssVarName, !slotEl.hidden ? slotEl.offsetHeight : 0);
|
||||
}
|
||||
|
||||
function createChromeController(showAppChrome) {
|
||||
function createChromeController(showAppChrome, initialShellMode = {}) {
|
||||
let topbarNode = null;
|
||||
let composerNode = null;
|
||||
|
||||
const cleanupOwnedNode = (node) => {
|
||||
if (node && typeof node.cleanup === 'function') node.cleanup();
|
||||
};
|
||||
let shellMode = normalizeShellMode(initialShellMode, showAppChrome);
|
||||
let disposed = false;
|
||||
|
||||
const apply = () => {
|
||||
if (disposed) return;
|
||||
applyShellMode(shellMode, showAppChrome);
|
||||
if (!showAppChrome) {
|
||||
clearSlot(topbarEl, '--topbar-height');
|
||||
clearSlot(composerEl, '--composer-height');
|
||||
clearSlot(topbarEl, '--topbar-height', 'app-shell--has-topbar');
|
||||
clearSlot(composerEl, '--composer-height', 'app-shell--has-composer');
|
||||
return;
|
||||
}
|
||||
mountSlot(topbarEl, '--topbar-height', topbarNode);
|
||||
mountSlot(composerEl, '--composer-height', composerNode);
|
||||
mountSlot(topbarEl, '--topbar-height', topbarNode, 'app-shell--has-topbar');
|
||||
mountSlot(composerEl, '--composer-height', composerNode, 'app-shell--has-composer');
|
||||
topbarHeightObserver?.sync?.();
|
||||
composerHeightObserver?.sync?.();
|
||||
};
|
||||
|
||||
apply();
|
||||
|
||||
return {
|
||||
setTopbar(node = null) {
|
||||
topbarNode = node instanceof Node ? node : null;
|
||||
const nextTopbar = node instanceof Node ? node : null;
|
||||
if (topbarNode && topbarNode !== nextTopbar) cleanupOwnedNode(topbarNode);
|
||||
topbarNode = nextTopbar;
|
||||
apply();
|
||||
},
|
||||
setComposer(node = null) {
|
||||
composerNode = node instanceof Node ? node : null;
|
||||
const nextComposer = node instanceof Node ? node : null;
|
||||
if (composerNode && composerNode !== nextComposer) cleanupOwnedNode(composerNode);
|
||||
composerNode = nextComposer;
|
||||
apply();
|
||||
},
|
||||
setShellMode(nextMode = {}) {
|
||||
shellMode = normalizeShellMode({ ...shellMode, ...(nextMode || {}) }, showAppChrome);
|
||||
apply();
|
||||
},
|
||||
clear() {
|
||||
cleanupOwnedNode(topbarNode);
|
||||
cleanupOwnedNode(composerNode);
|
||||
topbarNode = null;
|
||||
composerNode = null;
|
||||
apply();
|
||||
},
|
||||
suspend() {
|
||||
clearSlot(topbarEl, '--topbar-height');
|
||||
clearSlot(composerEl, '--composer-height');
|
||||
clearSlot(topbarEl, '--topbar-height', 'app-shell--has-topbar');
|
||||
clearSlot(composerEl, '--composer-height', 'app-shell--has-composer');
|
||||
},
|
||||
resume() {
|
||||
apply();
|
||||
},
|
||||
dispose() {
|
||||
cleanupOwnedNode(topbarNode);
|
||||
cleanupOwnedNode(composerNode);
|
||||
topbarNode = null;
|
||||
composerNode = null;
|
||||
disposed = true;
|
||||
clearSlot(topbarEl, '--topbar-height');
|
||||
clearSlot(composerEl, '--composer-height');
|
||||
clearSlot(topbarEl, '--topbar-height', 'app-shell--has-topbar');
|
||||
clearSlot(composerEl, '--composer-height', 'app-shell--has-composer');
|
||||
resetShellMode();
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function clearKeepAliveEntries() {
|
||||
currentCleanup = null;
|
||||
currentChromeCleanup = null;
|
||||
if (typeof currentCleanup === 'function') {
|
||||
currentCleanup();
|
||||
currentCleanup = null;
|
||||
}
|
||||
if (typeof currentChromeCleanup === 'function') {
|
||||
currentChromeCleanup();
|
||||
currentChromeCleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function unlockHiddenDmAudio() {
|
||||
@@ -1244,14 +1348,15 @@ function renderPageFailureFallback(pageId, error) {
|
||||
wrap.append(card);
|
||||
screenEl.append(wrap);
|
||||
|
||||
resetManagedScreenClasses();
|
||||
screenEl.classList.toggle('no-app-chrome', false);
|
||||
if (typeof currentChromeCleanup === 'function') {
|
||||
currentChromeCleanup();
|
||||
currentChromeCleanup = null;
|
||||
}
|
||||
clearSlot(topbarEl, '--topbar-height');
|
||||
clearSlot(composerEl, '--composer-height');
|
||||
toolbarEl.innerHTML = '';
|
||||
clearSlot(topbarEl, '--topbar-height', 'app-shell--has-topbar');
|
||||
clearSlot(composerEl, '--composer-height', 'app-shell--has-composer');
|
||||
clearSlot(toolbarEl, '--toolbar-height');
|
||||
toolbarHeightObserver?.sync?.();
|
||||
refreshConnectionUi();
|
||||
}
|
||||
@@ -1292,10 +1397,23 @@ function renderApp() {
|
||||
currentChromeCleanup = null;
|
||||
}
|
||||
|
||||
// Сначала полностью очищаем page-owned UI предыдущего маршрута, затем
|
||||
// применяем новый shell mode и только после этого монтируем следующий экран.
|
||||
screenEl.innerHTML = '';
|
||||
resetManagedScreenClasses();
|
||||
clearSlot(toolbarEl, '--toolbar-height');
|
||||
screenEl.classList.toggle('no-app-chrome', !showAppChrome);
|
||||
screenEl.classList.toggle('preauth-flow', PRE_AUTH_PAGES.includes(pageId));
|
||||
screenEl.classList.toggle('filled-action-buttons', FILLED_ACTION_BUTTON_PAGE_IDS.has(pageId));
|
||||
screenEl.classList.toggle('settings-bordered-actions', SETTINGS_BORDERED_ACTION_PAGE_IDS.has(pageId));
|
||||
|
||||
try {
|
||||
screenEl.innerHTML = '';
|
||||
const chrome = createChromeController(showAppChrome);
|
||||
const chrome = createChromeController(showAppChrome, page.pageMeta?.shellMode);
|
||||
currentChromeCleanup = () => chrome.dispose();
|
||||
if (showAppChrome) {
|
||||
mountSlot(toolbarEl, '--toolbar-height', renderToolbar(page.pageMeta.id, navigate));
|
||||
toolbarHeightObserver?.sync?.();
|
||||
}
|
||||
const screen = page.render({ route, navigate, chrome });
|
||||
if (!(screen instanceof Node)) {
|
||||
throw new Error('Page render returned invalid node');
|
||||
@@ -1309,16 +1427,6 @@ function renderApp() {
|
||||
scrollToBottomControl?.cleanup();
|
||||
};
|
||||
|
||||
screenEl.classList.toggle('no-app-chrome', !showAppChrome);
|
||||
screenEl.classList.toggle('preauth-flow', PRE_AUTH_PAGES.includes(pageId));
|
||||
screenEl.classList.toggle('filled-action-buttons', FILLED_ACTION_BUTTON_PAGE_IDS.has(pageId));
|
||||
screenEl.classList.toggle('settings-bordered-actions', SETTINGS_BORDERED_ACTION_PAGE_IDS.has(pageId));
|
||||
|
||||
toolbarEl.innerHTML = '';
|
||||
if (showAppChrome) {
|
||||
toolbarEl.append(renderToolbar(page.pageMeta.id, navigate));
|
||||
}
|
||||
toolbarHeightObserver?.sync?.();
|
||||
refreshConnectionUi();
|
||||
} catch (error) {
|
||||
console.error('[renderApp] controlled fallback', error);
|
||||
@@ -1333,9 +1441,9 @@ function refreshToolbarOnly() {
|
||||
const showAppChrome = page.pageMeta?.showAppChrome !== false
|
||||
&& !(pageId === 'language-view' && !state.session.isAuthorized);
|
||||
|
||||
toolbarEl.innerHTML = '';
|
||||
clearSlot(toolbarEl, '--toolbar-height');
|
||||
if (showAppChrome) {
|
||||
toolbarEl.append(renderToolbar(page.pageMeta.id, navigate));
|
||||
mountSlot(toolbarEl, '--toolbar-height', renderToolbar(page.pageMeta.id, navigate));
|
||||
}
|
||||
toolbarHeightObserver?.sync?.();
|
||||
refreshConnectionUi();
|
||||
|
||||
Reference in New Issue
Block a user