From 02d2a6c70b07f8d9d75179eebcdd4d3c9f8851d6 Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 9 Jun 2026 17:16:00 +0800 Subject: [PATCH] fix: mobile hub AI keyboard layout and instance top nav scroll Sync hub shell to visualViewport when the keyboard opens to prevent white screen above chat input. Make instance tabs horizontally scrollable with active tab centered on phone. Co-authored-by: Cursor --- crypto_monitor_binance/templates/index.html | 5 +- crypto_monitor_gate/templates/index.html | 5 +- crypto_monitor_gate_bot/templates/index.html | 5 +- crypto_monitor_okx/templates/index.html | 5 +- manual_trading_hub/static/app.css | 36 ++++++++--- manual_trading_hub/static/app.js | 66 ++++++++++++++++++++ manual_trading_hub/static/index.html | 6 +- static/instance_theme.css | 27 ++++++-- static/instance_theme.js | 27 ++++++++ 9 files changed, 159 insertions(+), 23 deletions(-) diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index a2c436a..106a469 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -2,7 +2,8 @@ - + + @@ -232,7 +233,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index 1e4dc09..934860a 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -2,7 +2,8 @@ - + + @@ -232,7 +233,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index 6270f86..907cc9a 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -2,7 +2,8 @@ - + + @@ -269,7 +270,7 @@ .stats-split-row{grid-template-columns:1fr} } - + diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index 0d799db..7cc0e0e 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -2,7 +2,8 @@ - + + @@ -232,7 +233,7 @@ .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} - + diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 787f0fa..44c8d40 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -3815,15 +3815,31 @@ body.hub-page-ai #page-ai { @media (max-width: 720px) { body.hub-page-ai .app-shell { padding-bottom: max(8px, env(safe-area-inset-bottom)); - height: 100dvh; - max-height: 100dvh; + height: var(--hub-vvh, 100dvh); + max-height: var(--hub-vvh, 100dvh); overflow: hidden; width: 100%; max-width: none; + box-sizing: border-box; + will-change: transform, height; } body.hub-page-ai { overflow: hidden; + background: var(--bg); + } + + body.hub-page-ai.hub-ai-keyboard-open .app-header, + body.hub-page-ai.hub-ai-keyboard-open #page-ai .page-head { + display: none; + } + + body.hub-page-ai.hub-ai-keyboard-open .ai-mobile-tabs { + margin-bottom: 4px; + } + + body.hub-page-ai.hub-ai-keyboard-open .ai-panel-head { + padding-bottom: 0; } body.hub-page-ai #page-ai { @@ -3896,23 +3912,29 @@ body.hub-page-ai #page-ai { body.hub-page-ai .ai-chat-panel { padding-bottom: 0; + display: flex; + flex-direction: column; + overflow: hidden; } body.hub-page-ai .ai-chat-messages { + flex: 1 1 auto; + min-height: 0; padding-bottom: 8px; + overflow-y: auto; + -webkit-overflow-scrolling: touch; } body.hub-page-ai .ai-chat-form { - position: sticky; - bottom: 0; + position: relative; + flex-shrink: 0; z-index: 3; width: 100%; margin: 0; padding: 10px 0 max(10px, env(safe-area-inset-bottom)); - background: color-mix(in srgb, var(--panel) 92%, transparent); - backdrop-filter: blur(10px); + background: var(--panel); border-top: 1px solid var(--border-soft); - box-shadow: 0 -10px 28px rgba(0, 0, 0, 0.28); + box-shadow: 0 -6px 18px rgba(0, 0, 0, 0.18); } body.hub-page-ai .ai-chat-form textarea { diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 9586382..6471561 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -652,6 +652,7 @@ el.classList.toggle("hidden", el.id !== pageId); }); document.body.classList.toggle("hub-page-ai", page === "ai"); + syncHubAiMobileViewport(); if (page === "monitor") startMonitorPoll(); else stopMonitorPoll(); if (page === "settings") loadSettingsUI(); @@ -1005,8 +1006,73 @@ applyAiMobileTab(); } + let syncHubAiMobileViewport = () => {}; + + function initHubAiMobileViewport() { + const mq = window.matchMedia("(max-width: 720px)"); + const shell = document.querySelector(".app-shell"); + const chatInput = document.getElementById("ai-chat-input"); + if (!shell || !window.visualViewport) { + syncHubAiMobileViewport = () => {}; + return; + } + + const scrollChatToEnd = () => { + const box = document.getElementById("ai-chat-messages"); + if (box) requestAnimationFrame(() => { box.scrollTop = box.scrollHeight; }); + }; + + syncHubAiMobileViewport = () => { + const onAi = document.body.classList.contains("hub-page-ai"); + if (!onAi || !mq.matches) { + shell.style.removeProperty("height"); + shell.style.removeProperty("max-height"); + shell.style.removeProperty("width"); + shell.style.removeProperty("transform"); + document.documentElement.style.removeProperty("--hub-vvh"); + document.body.classList.remove("hub-ai-keyboard-open"); + return; + } + const vv = window.visualViewport; + const h = Math.max(240, Math.round(vv.height)); + const top = Math.round(vv.offsetTop || 0); + const left = Math.round(vv.offsetLeft || 0); + document.documentElement.style.setProperty("--hub-vvh", `${h}px`); + shell.style.height = `${h}px`; + shell.style.maxHeight = `${h}px`; + shell.style.width = `${Math.round(vv.width)}px`; + shell.style.transform = + top > 0 || left > 0 ? `translate(${left}px, ${top}px)` : ""; + const keyboardLikely = + top > 0 || h < window.innerHeight * 0.82 || document.activeElement === chatInput; + document.body.classList.toggle("hub-ai-keyboard-open", keyboardLikely); + }; + + window.visualViewport.addEventListener("resize", syncHubAiMobileViewport); + window.visualViewport.addEventListener("scroll", syncHubAiMobileViewport); + window.addEventListener("resize", syncHubAiMobileViewport); + window.addEventListener("orientationchange", () => { + setTimeout(syncHubAiMobileViewport, 80); + }); + + if (chatInput) { + chatInput.addEventListener("focus", () => { + syncHubAiMobileViewport(); + scrollChatToEnd(); + setTimeout(syncHubAiMobileViewport, 50); + setTimeout(syncHubAiMobileViewport, 280); + }); + chatInput.addEventListener("blur", () => { + setTimeout(syncHubAiMobileViewport, 80); + setTimeout(syncHubAiMobileViewport, 320); + }); + } + syncHubAiMobileViewport(); + } + function initMobileLayout() { initAiMobileTabs(); + initHubAiMobileViewport(); let resizeTimer = null; let wasMobile = isMobileLayout(); window.addEventListener("resize", () => { diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 1f09803..6723e18 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -3,7 +3,7 @@ - + @@ -15,7 +15,7 @@ - + @@ -424,6 +424,6 @@ - + diff --git a/static/instance_theme.css b/static/instance_theme.css index 2b1667c..56e3dd9 100644 --- a/static/instance_theme.css +++ b/static/instance_theme.css @@ -27,14 +27,31 @@ gap: 8px; } + .container { + max-width: 100% !important; + width: 100% !important; + padding-left: 0 !important; + padding-right: 0 !important; + overflow: visible !important; + } + .top-nav { - display: flex; - flex-wrap: nowrap; - overflow-x: auto; + display: flex !important; + flex-wrap: nowrap !important; + justify-content: flex-start !important; + align-items: stretch; + overflow-x: auto !important; + overflow-y: hidden; + width: 100%; + max-width: 100%; -webkit-overflow-scrolling: touch; + overscroll-behavior-x: contain; scrollbar-width: none; - gap: 6px; - padding-bottom: 2px; + gap: 6px !important; + margin-bottom: 12px !important; + padding: 2px 2px 6px; + scroll-padding-inline: 10px; + touch-action: pan-x; } .top-nav::-webkit-scrollbar { diff --git a/static/instance_theme.js b/static/instance_theme.js index fd42da5..6c5846a 100644 --- a/static/instance_theme.js +++ b/static/instance_theme.js @@ -251,6 +251,32 @@ }); } + function initMobileTopNav() { + const mq = window.matchMedia("(max-width: 720px)"); + + function scrollActiveTab(nav) { + const active = nav.querySelector("a.active"); + if (!active) return; + requestAnimationFrame(() => { + try { + active.scrollIntoView({ inline: "center", block: "nearest", behavior: "instant" }); + } catch (_) { + active.scrollIntoView(false); + } + }); + } + + function apply() { + if (!mq.matches) return; + document.querySelectorAll(".top-nav").forEach(scrollActiveTab); + } + + apply(); + mq.addEventListener("change", apply); + window.addEventListener("resize", apply); + window.addEventListener("orientationchange", apply); + } + function initFromHubMessage(data) { if (!data || data.type !== "hub-theme-sync") return; if (!isHubLinked()) return; @@ -285,6 +311,7 @@ const onReady = () => { initToggleUI(); + initMobileTopNav(); syncInlineStyles(get()); patchHubNavLinks(get()); observeDynamicLists();