fix: stabilize mini app mobile navigation
This commit is contained in:
@@ -1474,7 +1474,7 @@ def _resolve_hashed_js_asset_name(*, kind: str, base_name: str) -> str:
|
|||||||
if minified_assets:
|
if minified_assets:
|
||||||
minified_assets.sort(reverse=True)
|
minified_assets.sort(reverse=True)
|
||||||
return _set_cached_asset_name(kind, minified_assets[0][1])
|
return _set_cached_asset_name(kind, minified_assets[0][1])
|
||||||
return _set_cached_asset_name(kind, f"{base_name}.js")
|
return _set_cached_asset_name(kind, _stable_asset_name_with_version(f"{base_name}.js"))
|
||||||
|
|
||||||
|
|
||||||
def _resolve_webapp_css_asset_name() -> str:
|
def _resolve_webapp_css_asset_name() -> str:
|
||||||
@@ -1506,7 +1506,19 @@ def _resolve_hashed_css_asset_name(*, kind: str, base_name: str) -> str:
|
|||||||
if hashed_assets:
|
if hashed_assets:
|
||||||
hashed_assets.sort(reverse=True)
|
hashed_assets.sort(reverse=True)
|
||||||
return _set_cached_asset_name(kind, hashed_assets[0][1])
|
return _set_cached_asset_name(kind, hashed_assets[0][1])
|
||||||
return _set_cached_asset_name(kind, f"{base_name}.css")
|
return _set_cached_asset_name(kind, _stable_asset_name_with_version(f"{base_name}.css"))
|
||||||
|
|
||||||
|
|
||||||
|
def _stable_asset_name_with_version(filename: str) -> str:
|
||||||
|
path = ASSET_DIR / filename
|
||||||
|
try:
|
||||||
|
stat = path.stat()
|
||||||
|
except OSError:
|
||||||
|
return filename
|
||||||
|
|
||||||
|
raw_version = f"{filename}:{int(stat.st_mtime_ns)}:{int(stat.st_size)}"
|
||||||
|
version = hashlib.sha256(raw_version.encode("utf-8")).hexdigest()[:8]
|
||||||
|
return f"{filename}?v={version}"
|
||||||
|
|
||||||
|
|
||||||
def _get_cached_asset_name(kind: str) -> Optional[str]:
|
def _get_cached_asset_name(kind: str) -> Optional[str]:
|
||||||
|
|||||||
@@ -2154,8 +2154,11 @@ a {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(var(--bottom-nav-visible-items, 3), minmax(0, 1fr));
|
grid-template-columns: repeat(var(--bottom-nav-visible-items, 3), minmax(0, 1fr));
|
||||||
grid-auto-flow: column;
|
grid-auto-flow: column;
|
||||||
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
grid-auto-rows: 1fr;
|
||||||
|
height: var(--bottom-nav-height);
|
||||||
gap: 2px;
|
gap: 2px;
|
||||||
min-height: 64px;
|
min-height: var(--bottom-nav-height);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
@@ -2174,7 +2177,11 @@ a {
|
|||||||
|
|
||||||
.bottom-nav button {
|
.bottom-nav button {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
grid-template-rows: 22px minmax(0, 12px);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -2185,6 +2192,13 @@ a {
|
|||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 6px 2px;
|
padding: 6px 2px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-nav button > svg {
|
||||||
|
width: 21px;
|
||||||
|
height: 21px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-nav button.active {
|
.bottom-nav button.active {
|
||||||
@@ -2193,10 +2207,12 @@ a {
|
|||||||
|
|
||||||
.bottom-nav .bottom-nav-label {
|
.bottom-nav .bottom-nav-label {
|
||||||
display: block;
|
display: block;
|
||||||
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
font-size: 10px;
|
||||||
line-height: 1.1;
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2204,12 +2220,40 @@ a {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 460px) {
|
||||||
|
.bottom-nav.bottom-nav-many {
|
||||||
|
height: 58px;
|
||||||
|
min-height: 58px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-nav.bottom-nav-many button {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
|
gap: 0;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-nav.bottom-nav-many .bottom-nav-label {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-nav.bottom-nav-many .nav-attention-dot {
|
||||||
|
top: 8px;
|
||||||
|
right: calc(50% - 18px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-nav.bottom-nav-many .nav-badge-floating {
|
||||||
|
top: 6px;
|
||||||
|
right: calc(50% - 24px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 380px) {
|
@media (max-width: 380px) {
|
||||||
.bottom-nav.bottom-nav-many {
|
.bottom-nav.bottom-nav-many {
|
||||||
min-height: 58px;
|
min-height: 58px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-nav.bottom-nav-many button {
|
.bottom-nav.bottom-nav-many button {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
}
|
}
|
||||||
@@ -2231,10 +2275,12 @@ a {
|
|||||||
|
|
||||||
@media (max-width: 360px) {
|
@media (max-width: 360px) {
|
||||||
.bottom-nav {
|
.bottom-nav {
|
||||||
|
height: 56px;
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bottom-nav button {
|
.bottom-nav button {
|
||||||
|
grid-template-rows: 1fr;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
padding: 0 2px;
|
padding: 0 2px;
|
||||||
}
|
}
|
||||||
@@ -3149,6 +3195,7 @@ a {
|
|||||||
display: grid !important;
|
display: grid !important;
|
||||||
grid-template-columns: 22px 1fr !important;
|
grid-template-columns: 22px 1fr !important;
|
||||||
grid-template-rows: auto !important;
|
grid-template-rows: auto !important;
|
||||||
|
height: auto !important;
|
||||||
align-items: center !important;
|
align-items: center !important;
|
||||||
justify-items: start !important;
|
justify-items: start !important;
|
||||||
gap: 12px !important;
|
gap: 12px !important;
|
||||||
|
|||||||
@@ -335,6 +335,15 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
self.assertIn("<title>Subscription</title>", html)
|
self.assertIn("<title>Subscription</title>", html)
|
||||||
self.assertLess(html.index("/subscription_webapp.css"), html.index("WEBAPP_JS_SCRIPT"))
|
self.assertLess(html.index("/subscription_webapp.css"), html.index("WEBAPP_JS_SCRIPT"))
|
||||||
|
|
||||||
|
def test_mobile_bottom_nav_many_items_uses_compact_phone_layout(self):
|
||||||
|
css_path = Path(__file__).resolve().parents[1] / "frontend/src/styles/webapp.css"
|
||||||
|
css = css_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
self.assertIn("@media (max-width: 460px)", css)
|
||||||
|
self.assertIn(".bottom-nav.bottom-nav-many", css)
|
||||||
|
self.assertIn(".bottom-nav.bottom-nav-many .bottom-nav-label", css)
|
||||||
|
self.assertIn("display: none;", css)
|
||||||
|
|
||||||
def test_https_webapp_logo_uses_same_origin_proxy(self):
|
def test_https_webapp_logo_uses_same_origin_proxy(self):
|
||||||
settings = SimpleNamespace(WEBAPP_LOGO_URL="https://cdn.example.com/logo.png")
|
settings = SimpleNamespace(WEBAPP_LOGO_URL="https://cdn.example.com/logo.png")
|
||||||
|
|
||||||
@@ -981,6 +990,25 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
|
|||||||
"subscription_webapp.min.22222222.js",
|
"subscription_webapp.min.22222222.js",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_resolve_webapp_asset_names_version_stable_fallbacks(self):
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
asset_dir = Path(tmpdir)
|
||||||
|
(asset_dir / "subscription_webapp.js").write_text(
|
||||||
|
"console.log('fallback');", encoding="utf-8"
|
||||||
|
)
|
||||||
|
(asset_dir / "subscription_webapp.css").write_text(".app{color:red}", encoding="utf-8")
|
||||||
|
|
||||||
|
with patch.object(webapp_assets, "ASSET_DIR", asset_dir):
|
||||||
|
webapp_assets._ASSET_NAME_CACHE.clear()
|
||||||
|
self.assertRegex(
|
||||||
|
subscription_webapp._resolve_webapp_js_asset_name(),
|
||||||
|
r"^subscription_webapp\.js\?v=[0-9a-f]{8}$",
|
||||||
|
)
|
||||||
|
self.assertRegex(
|
||||||
|
subscription_webapp._resolve_webapp_css_asset_name(),
|
||||||
|
r"^subscription_webapp\.css\?v=[0-9a-f]{8}$",
|
||||||
|
)
|
||||||
|
|
||||||
def test_resolve_webapp_admin_asset_names_use_stable_runtime_builds(self):
|
def test_resolve_webapp_admin_asset_names_use_stable_runtime_builds(self):
|
||||||
with tempfile.TemporaryDirectory() as tmpdir:
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
asset_dir = Path(tmpdir)
|
asset_dir = Path(tmpdir)
|
||||||
|
|||||||
Reference in New Issue
Block a user