SHA256
UI: свайпы вкладок и навигация тредов
This commit is contained in:
+493
-8
@@ -1,4 +1,14 @@
|
||||
import { navigate, getRoute, PRE_AUTH_PAGES } from './router.js';
|
||||
import {
|
||||
navigate,
|
||||
getRoute,
|
||||
parseRouteFromPath,
|
||||
PRE_AUTH_PAGES,
|
||||
getSwipeNavigationTarget,
|
||||
syncTrackedRouteHistory,
|
||||
rememberToolbarRoute,
|
||||
resetRememberedToolbarRoutes,
|
||||
resolveToolbarActive,
|
||||
} from './router.js';
|
||||
import { renderToolbar } from './components/toolbar.js';
|
||||
import { captureClientError, setClientErrorSentNotifier, setClientErrorTransport } from './services/client-error-reporter.js';
|
||||
import { initPwaInstallPromptHandling } from './services/pwa-install-service.js';
|
||||
@@ -163,6 +173,14 @@ const SIGNED_DM_DECRYPT_CONTEXT_POLL_MS = 50;
|
||||
const UI_VERSION_PERIODIC_CHECK_MS = 5 * 60 * 1000;
|
||||
const CURRENT_BUILD_HASH = String(window.__SHINE_BUILD_HASH__ || '').trim();
|
||||
const UI_BUILD_HASH_PATTERN = /window\.__SHINE_BUILD_HASH__\s*=\s*'([^']+)'/;
|
||||
const KEEP_ALIVE_ROOTS = new Set(['messages-list', 'channels-list']);
|
||||
const HORIZONTAL_SWIPE_MIN_DISTANCE_PX = 72;
|
||||
const HORIZONTAL_SWIPE_MAX_VERTICAL_DRIFT_PX = 56;
|
||||
const HORIZONTAL_SWIPE_DOMINANCE_RATIO = 1.35;
|
||||
const HORIZONTAL_SWIPE_LOCK_DISTANCE_PX = 14;
|
||||
const HORIZONTAL_SWIPE_COMMIT_RATIO = 0.32;
|
||||
const HORIZONTAL_SWIPE_PREVIEW_EDGE_PX = 18;
|
||||
const HORIZONTAL_SWIPE_MAX_DURATION_MS = 260;
|
||||
|
||||
let currentCleanup = null;
|
||||
let pingIntervalId = null;
|
||||
@@ -184,6 +202,9 @@ let hiddenDmAudioUnlocked = false;
|
||||
let initialConnectionCompleted = false;
|
||||
let orientationLockInFlight = false;
|
||||
let currentChromeCleanup = null;
|
||||
let currentMountState = null;
|
||||
let activeSwipePreview = null;
|
||||
const keepAliveEntries = new Map();
|
||||
const CALL_PUSH_PENDING_ACTION_KEY = 'shine-ui-call-push-pending-action-v1';
|
||||
const GUEST_ALLOWED_PAGES = new Set([
|
||||
'start-view',
|
||||
@@ -283,6 +304,13 @@ function createChromeController(showAppChrome) {
|
||||
composerNode = null;
|
||||
apply();
|
||||
},
|
||||
suspend() {
|
||||
clearSlot(topbarEl, '--topbar-height');
|
||||
clearSlot(composerEl, '--composer-height');
|
||||
},
|
||||
resume() {
|
||||
apply();
|
||||
},
|
||||
dispose() {
|
||||
disposed = true;
|
||||
clearSlot(topbarEl, '--topbar-height');
|
||||
@@ -291,6 +319,403 @@ function createChromeController(showAppChrome) {
|
||||
};
|
||||
}
|
||||
|
||||
function destroyMountState(entry) {
|
||||
if (!entry) return;
|
||||
if (entry.destroyed) return;
|
||||
entry.destroyed = true;
|
||||
try {
|
||||
if (typeof entry.cleanup === 'function') {
|
||||
entry.cleanup();
|
||||
}
|
||||
} finally {
|
||||
entry.chrome?.dispose?.();
|
||||
}
|
||||
}
|
||||
|
||||
function clearKeepAliveEntries() {
|
||||
teardownSwipePreview({ cancelOnly: true });
|
||||
keepAliveEntries.forEach((entry) => destroyMountState(entry));
|
||||
keepAliveEntries.clear();
|
||||
resetRememberedToolbarRoutes();
|
||||
currentMountState = null;
|
||||
currentCleanup = null;
|
||||
currentChromeCleanup = null;
|
||||
}
|
||||
|
||||
function detachMountedScreen(entry) {
|
||||
if (!entry) return;
|
||||
entry.chrome?.suspend?.();
|
||||
if (entry.screen?.parentNode === screenEl) {
|
||||
screenEl.removeChild(entry.screen);
|
||||
} else {
|
||||
screenEl.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
function mountExistingEntry(entry, { showAppChrome, pageId }) {
|
||||
teardownSwipePreview({ cancelOnly: true });
|
||||
screenEl.innerHTML = '';
|
||||
screenEl.append(entry.screen);
|
||||
entry.chrome?.resume?.();
|
||||
currentMountState = entry;
|
||||
currentCleanup = typeof entry.cleanup === 'function' ? entry.cleanup : null;
|
||||
currentChromeCleanup = () => entry.chrome?.dispose?.();
|
||||
screenEl.classList.toggle('no-app-chrome', !showAppChrome);
|
||||
screenEl.classList.toggle('preauth-flow', PRE_AUTH_PAGES.includes(pageId));
|
||||
}
|
||||
|
||||
function cloneScreenForSwipe(screen) {
|
||||
const clone = screen?.cloneNode?.(true);
|
||||
if (!(clone instanceof Node)) return null;
|
||||
return clone;
|
||||
}
|
||||
|
||||
function sanitizeSwipeClone(node) {
|
||||
if (!(node instanceof Element)) return;
|
||||
node.removeAttribute('id');
|
||||
node.querySelectorAll('[id]').forEach((el) => el.removeAttribute('id'));
|
||||
}
|
||||
|
||||
function cloneSlotChildForSwipe(slotEl) {
|
||||
const child = slotEl?.firstElementChild;
|
||||
if (!(child instanceof Node)) return null;
|
||||
const clone = child.cloneNode(true);
|
||||
if (clone instanceof Element) sanitizeSwipeClone(clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
function createSwipeFrameSlot(className, contentNode = null) {
|
||||
const slot = document.createElement('div');
|
||||
slot.className = className;
|
||||
if (contentNode instanceof Node) {
|
||||
slot.append(contentNode);
|
||||
slot.hidden = false;
|
||||
} else {
|
||||
slot.hidden = true;
|
||||
}
|
||||
return slot;
|
||||
}
|
||||
|
||||
function buildSwipePane({
|
||||
topbarNode = null,
|
||||
screenNode = null,
|
||||
composerNode = null,
|
||||
screenClassName = '',
|
||||
screenScrollTop = 0,
|
||||
}) {
|
||||
const pane = document.createElement('div');
|
||||
pane.className = 'screen-swipe-pane';
|
||||
|
||||
const topbarSlot = createSwipeFrameSlot('topbar-slot screen-swipe-slot screen-swipe-slot--topbar', topbarNode);
|
||||
const screenSlot = document.createElement('main');
|
||||
screenSlot.className = `${screenClassName || 'screen-content'} screen-swipe-slot screen-swipe-slot--content`;
|
||||
if (screenNode instanceof Node) {
|
||||
screenSlot.append(screenNode);
|
||||
}
|
||||
const composerSlot = createSwipeFrameSlot('composer-slot screen-swipe-slot screen-swipe-slot--composer', composerNode);
|
||||
|
||||
pane.append(topbarSlot, screenSlot, composerSlot);
|
||||
requestAnimationFrame(() => {
|
||||
screenSlot.scrollTop = Math.max(0, Number(screenScrollTop || 0));
|
||||
});
|
||||
return pane;
|
||||
}
|
||||
|
||||
function createSwipePreviewTarget(targetPath) {
|
||||
const route = parseRouteFromPath(`/${String(targetPath || '').replace(/^\/+/, '')}`);
|
||||
const pageId = route.pageId || (state.session.isAuthorized ? 'messages-list' : 'start-view');
|
||||
const page = routes[pageId] || routes['start-view'];
|
||||
const rootPageId = resolveToolbarActive(pageId);
|
||||
const cachedEntry = keepAliveEntries.get(rootPageId);
|
||||
if (cachedEntry && cachedEntry.routePath === `/${String(targetPath || '').replace(/^\/+/, '')}`) {
|
||||
return {
|
||||
screen: cloneScreenForSwipe(cachedEntry.screen),
|
||||
cleanup: null,
|
||||
};
|
||||
}
|
||||
|
||||
let previewTopbarNode = null;
|
||||
let previewComposerNode = null;
|
||||
const chrome = {
|
||||
setTopbar(node = null) {
|
||||
previewTopbarNode = node instanceof Node ? node : null;
|
||||
},
|
||||
setComposer(node = null) {
|
||||
previewComposerNode = node instanceof Node ? node : null;
|
||||
},
|
||||
clear() {
|
||||
previewTopbarNode = null;
|
||||
previewComposerNode = null;
|
||||
},
|
||||
suspend() {},
|
||||
resume() {},
|
||||
dispose() {},
|
||||
};
|
||||
const screen = page.render({ route, navigate, chrome });
|
||||
if (!(screen instanceof Node)) {
|
||||
chrome.dispose();
|
||||
throw new Error('Swipe preview render returned invalid node');
|
||||
}
|
||||
const cleanup = typeof screen.cleanup === 'function' ? screen.cleanup : null;
|
||||
return {
|
||||
screen,
|
||||
topbarNode: previewTopbarNode,
|
||||
composerNode: previewComposerNode,
|
||||
cleanup: () => {
|
||||
try {
|
||||
if (typeof cleanup === 'function') cleanup();
|
||||
} finally {
|
||||
chrome.dispose();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function applySwipePreviewOffset(session, revealPx) {
|
||||
if (!session) return;
|
||||
const width = Math.max(1, session.width);
|
||||
const clamped = Math.max(0, Math.min(width, revealPx));
|
||||
session.revealPx = clamped;
|
||||
|
||||
const currentX = session.direction === 'left' ? -clamped : clamped;
|
||||
const targetX = session.direction === 'left'
|
||||
? width - clamped + HORIZONTAL_SWIPE_PREVIEW_EDGE_PX
|
||||
: -width + clamped - HORIZONTAL_SWIPE_PREVIEW_EDGE_PX;
|
||||
const dividerX = session.direction === 'left'
|
||||
? width - clamped
|
||||
: clamped;
|
||||
|
||||
session.currentPane.style.transform = `translate3d(${currentX}px, 0, 0)`;
|
||||
session.targetPane.style.transform = `translate3d(${targetX}px, 0, 0)`;
|
||||
session.divider.style.transform = `translate3d(${dividerX}px, 0, 0)`;
|
||||
|
||||
const overlayOpacity = Math.max(0.08, Math.min(0.24, (clamped / width) * 0.24));
|
||||
session.overlay.style.setProperty('--swipe-overlay-opacity', overlayOpacity.toFixed(3));
|
||||
}
|
||||
|
||||
function teardownSwipePreview({ cancelOnly = false } = {}) {
|
||||
const session = activeSwipePreview;
|
||||
if (!session) return;
|
||||
activeSwipePreview = null;
|
||||
|
||||
appShellEl?.classList.remove('app-shell--swiping');
|
||||
topbarEl?.classList.remove('topbar-slot--swipe-hidden');
|
||||
screenEl.classList.remove('screen-content--swipe-hidden');
|
||||
composerEl?.classList.remove('composer-slot--swipe-hidden');
|
||||
session.overlay.remove();
|
||||
if (typeof session.targetCleanup === 'function') {
|
||||
session.targetCleanup();
|
||||
}
|
||||
if (!cancelOnly) {
|
||||
session.onComplete?.();
|
||||
}
|
||||
}
|
||||
|
||||
function animateSwipePreviewTo(session, revealPx, { complete = false } = {}) {
|
||||
const width = Math.max(1, session.width);
|
||||
const currentReveal = Number(session.revealPx || 0);
|
||||
const remaining = Math.abs(revealPx - currentReveal);
|
||||
const duration = Math.max(140, Math.min(HORIZONTAL_SWIPE_MAX_DURATION_MS, Math.round((remaining / width) * HORIZONTAL_SWIPE_MAX_DURATION_MS)));
|
||||
|
||||
[session.currentPane, session.targetPane, session.divider].forEach((node) => {
|
||||
node.style.transition = `transform ${duration}ms cubic-bezier(0.22, 1, 0.36, 1)`;
|
||||
});
|
||||
session.overlay.style.transition = `opacity ${duration}ms ease`;
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
applySwipePreviewOffset(session, revealPx);
|
||||
if (!complete) {
|
||||
session.overlay.style.opacity = '0';
|
||||
}
|
||||
});
|
||||
|
||||
window.setTimeout(() => {
|
||||
teardownSwipePreview({ cancelOnly: !complete });
|
||||
}, duration + 24);
|
||||
}
|
||||
|
||||
function beginSwipePreview(direction, targetPath) {
|
||||
if (!currentMountState?.screen || activeSwipePreview) return null;
|
||||
const currentTopbarClone = cloneSlotChildForSwipe(topbarEl);
|
||||
const currentScreenClone = cloneScreenForSwipe(currentMountState.screen);
|
||||
const currentComposerClone = cloneSlotChildForSwipe(composerEl);
|
||||
if (!(currentScreenClone instanceof Node)) return null;
|
||||
if (currentTopbarClone instanceof Element) sanitizeSwipeClone(currentTopbarClone);
|
||||
if (currentScreenClone instanceof Element) sanitizeSwipeClone(currentScreenClone);
|
||||
if (currentComposerClone instanceof Element) sanitizeSwipeClone(currentComposerClone);
|
||||
|
||||
const targetPreview = createSwipePreviewTarget(targetPath);
|
||||
if (!(targetPreview?.screen instanceof Node)) {
|
||||
targetPreview?.cleanup?.();
|
||||
return null;
|
||||
}
|
||||
if (targetPreview.topbarNode instanceof Element) sanitizeSwipeClone(targetPreview.topbarNode);
|
||||
if (targetPreview.screen instanceof Element) sanitizeSwipeClone(targetPreview.screen);
|
||||
if (targetPreview.composerNode instanceof Element) sanitizeSwipeClone(targetPreview.composerNode);
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'screen-swipe-overlay';
|
||||
|
||||
const currentPane = buildSwipePane({
|
||||
topbarNode: currentTopbarClone,
|
||||
screenNode: currentScreenClone,
|
||||
composerNode: currentComposerClone,
|
||||
screenClassName: screenEl.className,
|
||||
screenScrollTop: screenEl.scrollTop,
|
||||
});
|
||||
currentPane.classList.add('screen-swipe-pane--current');
|
||||
|
||||
const targetPane = buildSwipePane({
|
||||
topbarNode: targetPreview.topbarNode || null,
|
||||
screenNode: targetPreview.screen,
|
||||
composerNode: targetPreview.composerNode || null,
|
||||
screenClassName: screenEl.className,
|
||||
screenScrollTop: 0,
|
||||
});
|
||||
targetPane.classList.add('screen-swipe-pane--target', `screen-swipe-pane--${direction}`);
|
||||
|
||||
const divider = document.createElement('div');
|
||||
divider.className = 'screen-swipe-divider';
|
||||
|
||||
overlay.append(currentPane, targetPane, divider);
|
||||
appShellEl.append(overlay);
|
||||
appShellEl?.classList.add('app-shell--swiping');
|
||||
topbarEl?.classList.add('topbar-slot--swipe-hidden');
|
||||
screenEl.classList.add('screen-content--swipe-hidden');
|
||||
composerEl?.classList.add('composer-slot--swipe-hidden');
|
||||
|
||||
const session = {
|
||||
direction,
|
||||
targetPath,
|
||||
width: screenEl.clientWidth || 1,
|
||||
overlay,
|
||||
currentPane,
|
||||
targetPane,
|
||||
divider,
|
||||
targetCleanup: targetPreview.cleanup || null,
|
||||
revealPx: 0,
|
||||
onComplete: () => navigate(targetPath),
|
||||
};
|
||||
activeSwipePreview = session;
|
||||
applySwipePreviewOffset(session, 0);
|
||||
return session;
|
||||
}
|
||||
|
||||
function installHorizontalTabSwipe() {
|
||||
if (!screenEl) return;
|
||||
|
||||
let touchStartX = 0;
|
||||
let touchStartY = 0;
|
||||
let touchActive = false;
|
||||
let touchBlocked = false;
|
||||
let swipeLocked = false;
|
||||
let swipeDirection = '';
|
||||
let swipeTargetPath = '';
|
||||
let swipeSession = null;
|
||||
|
||||
const reset = () => {
|
||||
touchActive = false;
|
||||
touchBlocked = false;
|
||||
swipeLocked = false;
|
||||
swipeDirection = '';
|
||||
swipeTargetPath = '';
|
||||
swipeSession = null;
|
||||
touchStartX = 0;
|
||||
touchStartY = 0;
|
||||
};
|
||||
|
||||
screenEl.addEventListener('touchstart', (event) => {
|
||||
if (event.touches.length !== 1) {
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
const target = event.target instanceof Element ? event.target : null;
|
||||
touchBlocked = Boolean(target?.closest('input, textarea, select, button, a, [contenteditable="true"]'));
|
||||
touchActive = !touchBlocked;
|
||||
touchStartX = Number(event.touches[0]?.clientX || 0);
|
||||
touchStartY = Number(event.touches[0]?.clientY || 0);
|
||||
}, { passive: true });
|
||||
|
||||
screenEl.addEventListener('touchmove', (event) => {
|
||||
if (!touchActive || touchBlocked) return;
|
||||
const touch = event.touches?.[0];
|
||||
const deltaX = Number(touch?.clientX || 0) - touchStartX;
|
||||
const deltaY = Number(touch?.clientY || 0) - touchStartY;
|
||||
const absX = Math.abs(deltaX);
|
||||
const absY = Math.abs(deltaY);
|
||||
|
||||
if (!swipeLocked) {
|
||||
if (absX < HORIZONTAL_SWIPE_LOCK_DISTANCE_PX && absY < HORIZONTAL_SWIPE_LOCK_DISTANCE_PX) return;
|
||||
if (absX <= absY * 1.05) {
|
||||
touchBlocked = true;
|
||||
return;
|
||||
}
|
||||
const currentPageId = getRoute().pageId || '';
|
||||
swipeDirection = deltaX < 0 ? 'left' : 'right';
|
||||
swipeTargetPath = getSwipeNavigationTarget(currentPageId, swipeDirection);
|
||||
if (!swipeTargetPath) {
|
||||
touchBlocked = true;
|
||||
return;
|
||||
}
|
||||
swipeSession = beginSwipePreview(swipeDirection, swipeTargetPath);
|
||||
if (!swipeSession) {
|
||||
touchBlocked = true;
|
||||
return;
|
||||
}
|
||||
swipeLocked = true;
|
||||
}
|
||||
|
||||
if (!swipeLocked || !swipeSession) return;
|
||||
event.preventDefault();
|
||||
|
||||
const revealPx = swipeDirection === 'left'
|
||||
? Math.max(0, -deltaX)
|
||||
: Math.max(0, deltaX);
|
||||
applySwipePreviewOffset(swipeSession, revealPx);
|
||||
}, { passive: false });
|
||||
|
||||
screenEl.addEventListener('touchcancel', reset, { passive: true });
|
||||
|
||||
screenEl.addEventListener('touchend', (event) => {
|
||||
if (!touchActive || touchBlocked) {
|
||||
if (swipeSession) {
|
||||
animateSwipePreviewTo(swipeSession, 0, { complete: false });
|
||||
}
|
||||
reset();
|
||||
return;
|
||||
}
|
||||
|
||||
const touch = event.changedTouches?.[0];
|
||||
const endX = Number(touch?.clientX || 0);
|
||||
const endY = Number(touch?.clientY || 0);
|
||||
const deltaX = endX - touchStartX;
|
||||
const deltaY = endY - touchStartY;
|
||||
const absX = Math.abs(deltaX);
|
||||
const absY = Math.abs(deltaY);
|
||||
const session = swipeSession;
|
||||
const wasLocked = swipeLocked;
|
||||
reset();
|
||||
|
||||
if (wasLocked && session) {
|
||||
event.preventDefault();
|
||||
const revealRatio = Number(session.revealPx || 0) / Math.max(1, session.width);
|
||||
const shouldCommit = revealRatio >= HORIZONTAL_SWIPE_COMMIT_RATIO;
|
||||
animateSwipePreviewTo(session, shouldCommit ? session.width : 0, { complete: shouldCommit });
|
||||
return;
|
||||
}
|
||||
|
||||
if (absX < HORIZONTAL_SWIPE_MIN_DISTANCE_PX) return;
|
||||
if (absY > HORIZONTAL_SWIPE_MAX_VERTICAL_DRIFT_PX) return;
|
||||
if (absX <= absY * HORIZONTAL_SWIPE_DOMINANCE_RATIO) return;
|
||||
|
||||
const currentPageId = getRoute().pageId || '';
|
||||
const direction = deltaX < 0 ? 'left' : 'right';
|
||||
const target = getSwipeNavigationTarget(currentPageId, direction);
|
||||
if (!target) return;
|
||||
navigate(target);
|
||||
}, { passive: true });
|
||||
}
|
||||
|
||||
async function unlockHiddenDmAudio() {
|
||||
try {
|
||||
const Ctx = window.AudioContext || window.webkitAudioContext;
|
||||
@@ -971,6 +1396,7 @@ function renderPageFailureFallback(pageId, error) {
|
||||
});
|
||||
|
||||
screenEl.innerHTML = '';
|
||||
teardownSwipePreview({ cancelOnly: true });
|
||||
const wrap = document.createElement('section');
|
||||
wrap.className = 'stack';
|
||||
|
||||
@@ -1007,6 +1433,8 @@ function renderPageFailureFallback(pageId, error) {
|
||||
}
|
||||
|
||||
function renderApp() {
|
||||
teardownSwipePreview({ cancelOnly: true });
|
||||
syncTrackedRouteHistory(window.location.pathname || '/');
|
||||
const route = getRoute();
|
||||
const pageId = route.pageId || (state.session.isAuthorized ? 'messages-list' : 'start-view');
|
||||
|
||||
@@ -1021,19 +1449,61 @@ function renderApp() {
|
||||
}
|
||||
|
||||
const page = routes[pageId] || routes['start-view'];
|
||||
const showAppChrome = page.pageMeta?.showAppChrome !== false;
|
||||
const rootPageId = resolveToolbarActive(pageId);
|
||||
const keepAliveEligible = showAppChrome && KEEP_ALIVE_ROOTS.has(rootPageId);
|
||||
const currentRoutePath = String(window.location.pathname || '/');
|
||||
|
||||
if (typeof currentCleanup === 'function') {
|
||||
currentCleanup();
|
||||
currentCleanup = null;
|
||||
rememberToolbarRoute(pageId);
|
||||
|
||||
if (currentMountState) {
|
||||
const shouldPreserveCurrent = currentMountState.keepAlive && currentMountState.rootPageId !== rootPageId;
|
||||
if (shouldPreserveCurrent) {
|
||||
currentMountState.routePath = currentMountState.routePath || currentRoutePath;
|
||||
keepAliveEntries.set(currentMountState.rootPageId, currentMountState);
|
||||
detachMountedScreen(currentMountState);
|
||||
currentMountState = null;
|
||||
currentCleanup = null;
|
||||
currentChromeCleanup = null;
|
||||
} else {
|
||||
destroyMountState(currentMountState);
|
||||
if (currentMountState.keepAlive) {
|
||||
keepAliveEntries.delete(currentMountState.rootPageId);
|
||||
}
|
||||
currentMountState = null;
|
||||
currentCleanup = null;
|
||||
currentChromeCleanup = null;
|
||||
}
|
||||
} else {
|
||||
if (typeof currentCleanup === 'function') {
|
||||
currentCleanup();
|
||||
currentCleanup = null;
|
||||
}
|
||||
if (typeof currentChromeCleanup === 'function') {
|
||||
currentChromeCleanup();
|
||||
currentChromeCleanup = null;
|
||||
}
|
||||
}
|
||||
if (typeof currentChromeCleanup === 'function') {
|
||||
currentChromeCleanup();
|
||||
currentChromeCleanup = null;
|
||||
|
||||
const cachedEntry = keepAliveEligible ? keepAliveEntries.get(rootPageId) : null;
|
||||
if (cachedEntry && cachedEntry.routePath === currentRoutePath) {
|
||||
mountExistingEntry(cachedEntry, { showAppChrome, pageId });
|
||||
toolbarEl.innerHTML = '';
|
||||
if (showAppChrome) {
|
||||
toolbarEl.append(renderToolbar(page.pageMeta.id, navigate));
|
||||
}
|
||||
toolbarHeightObserver?.sync?.();
|
||||
refreshConnectionUi();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cachedEntry) {
|
||||
destroyMountState(cachedEntry);
|
||||
keepAliveEntries.delete(rootPageId);
|
||||
}
|
||||
|
||||
try {
|
||||
screenEl.innerHTML = '';
|
||||
const showAppChrome = page.pageMeta?.showAppChrome !== false;
|
||||
const chrome = createChromeController(showAppChrome);
|
||||
currentChromeCleanup = () => chrome.dispose();
|
||||
const screen = page.render({ route, navigate, chrome });
|
||||
@@ -1043,6 +1513,19 @@ function renderApp() {
|
||||
|
||||
screenEl.append(screen);
|
||||
currentCleanup = typeof screen.cleanup === 'function' ? screen.cleanup : null;
|
||||
currentMountState = {
|
||||
pageId,
|
||||
rootPageId,
|
||||
keepAlive: keepAliveEligible,
|
||||
routePath: currentRoutePath,
|
||||
screen,
|
||||
cleanup: currentCleanup,
|
||||
chrome,
|
||||
destroyed: false,
|
||||
};
|
||||
if (keepAliveEligible) {
|
||||
keepAliveEntries.set(rootPageId, currentMountState);
|
||||
}
|
||||
|
||||
screenEl.classList.toggle('no-app-chrome', !showAppChrome);
|
||||
screenEl.classList.toggle('preauth-flow', PRE_AUTH_PAGES.includes(pageId));
|
||||
@@ -1163,6 +1646,7 @@ async function init() {
|
||||
setSessionResetHandler(() => {
|
||||
sessionRuntimeStarted = false;
|
||||
startConnectionMonitor();
|
||||
clearKeepAliveEntries();
|
||||
if (reconnectIntervalId) {
|
||||
window.clearInterval(reconnectIntervalId);
|
||||
reconnectIntervalId = null;
|
||||
@@ -1524,6 +2008,7 @@ async function init() {
|
||||
})();
|
||||
|
||||
window.addEventListener('popstate', renderApp);
|
||||
installHorizontalTabSwipe();
|
||||
document.addEventListener('pointerdown', () => {
|
||||
void unlockHiddenDmAudio();
|
||||
}, { passive: true });
|
||||
|
||||
Reference in New Issue
Block a user