@if (!isComposing)
{
@* on a phone a new post starts from a button under the thumb; from md, from the feed's tabs *@
@@ -27,7 +29,7 @@
}
-
@@ -141,6 +143,15 @@
string loadError;
bool isLoadingMore;
bool isComposing;
+ bool globeExpanded;
+
+ // a phone's banner (none when the phone is held sideways: the feed would have no room), the column from lg, or
+ // full screen
+ string GlobeClass => globeExpanded
+ ? "fixed inset-0 z-[70] block h-dvh w-full overflow-hidden bg-base-100"
+ : Filter.ShowGlobe
+ ? "block max-lg:short:hidden lg:block lg:order-last relative h-56 sm:h-64 lg:h-full flex-none min-h-0 rounded-xl neo-raised neo-xs overflow-hidden"
+ : "hidden lg:block lg:order-last relative h-56 sm:h-64 lg:h-full flex-none min-h-0 rounded-xl neo-raised neo-xs overflow-hidden";
(double Latitude, double Longitude)? position;
string positionError;
string loadedPersonaId;
diff --git a/Resources/AllStrings.it.resx b/Resources/AllStrings.it.resx
index e6b6410..9e6c511 100644
--- a/Resources/AllStrings.it.resx
+++ b/Resources/AllStrings.it.resx
@@ -1823,4 +1823,7 @@
Nuovo post
+
+ Apri il globo a schermo intero
+
diff --git a/Resources/AllStrings.resx b/Resources/AllStrings.resx
index 70a33f7..b4b8258 100644
--- a/Resources/AllStrings.resx
+++ b/Resources/AllStrings.resx
@@ -1823,4 +1823,7 @@
New post
+
+ Open the globe full screen
+
diff --git a/Styles/touch.css b/Styles/touch.css
index 1b7f305..9300f2f 100644
--- a/Styles/touch.css
+++ b/Styles/touch.css
@@ -28,6 +28,9 @@
}
}
+/* A short window: a phone held sideways. The globe's banner would leave the feed no room there. */
+@custom-variant short (@media (max-height: 30rem));
+
@layer utilities {
/* At every width: a checkbox's, radio's or toggle's label wraps (daisyUI's .label never does, and a long one ran past
a phone's edge), and the control keeps its size beside it. */
diff --git a/tests/e2e/Mobile/FlowTests.cs b/tests/e2e/Mobile/FlowTests.cs
index 107fea2..2491520 100644
--- a/tests/e2e/Mobile/FlowTests.cs
+++ b/tests/e2e/Mobile/FlowTests.cs
@@ -182,6 +182,49 @@ namespace decePubClient.E2E.Mobile
});
}
+ [Fact]
+ public async Task The_globe_opens_full_screen_from_its_banner_and_closes()
+ {
+ await using var session = await SignIn.As(browser, Persona(), Phone);
+ await Flow(session, "globe", async page =>
+ {
+ await page.Locator("button[title='Globe']").TapAsync();
+ var expand = page.Locator("[data-globe-expand]");
+ await Assertions.Expect(expand).ToBeVisibleAsync();
+ await expand.TapAsync();
+ var full = page.Locator("aside[role=dialog]");
+ await Assertions.Expect(full).ToBeVisibleAsync();
+ var box = (await full.BoundingBoxAsync())!;
+ var size = page.ViewportSize!;
+ Assert.True(Math.Abs(box.Width - size.Width) < 2 && Math.Abs(box.Height - size.Height) < 2,
+ $"the globe is {box.Width}x{box.Height}, not the screen's {size.Width}x{size.Height}");
+ await AssertWhole(page, "aside[role=dialog]");
+ await page.Locator("[data-globe-close]").TapAsync();
+ await Assertions.Expect(full).ToHaveCountAsync(0);
+ await Assertions.Expect(expand).ToBeVisibleAsync();
+ });
+ }
+
+ // a phone held sideways keeps its feed: no banner squeezing it, the globe full screen from the tabs instead
+ [Fact]
+ public async Task A_phone_held_sideways_keeps_its_feed()
+ {
+ await using var session = await SignIn.As(browser, Persona(), Phone.Pixel7Landscape);
+ var page = session.Page;
+ await Results.Cell("client.mobile.flow.landscape", "privapub", Phone.Pixel7Landscape.Key, async () =>
+ {
+ Assert.False(await page.Locator("[data-globe-expand]").IsVisibleAsync(), "the globe's banner squeezes the feed");
+ var feedHeight = await page.EvaluateAsync("() => document.getElementById('feed-scroll').clientHeight");
+ Assert.True(feedHeight >= 180, $"the feed is {feedHeight}px tall");
+ Assert.True(await page.EvaluateAsync("() => document.scrollingElement.scrollWidth <= innerWidth + 1"), "the page scrolls sideways");
+ await page.Locator("[data-globe-full]").TapAsync();
+ await Assertions.Expect(page.Locator("aside[role=dialog]")).ToBeVisibleAsync();
+ await page.Locator("[data-globe-close]").TapAsync();
+ await Assertions.Expect(page.Locator("aside[role=dialog]")).ToHaveCountAsync(0);
+ await session.AssertGuards();
+ }, page);
+ }
+
static async Task Latest(Pasture.Persona persona) =>
(await PersonaApi.Call(persona, $"/api/v1/accounts/{persona.LocalId}/statuses?limit=1"))!.AsArray().FirstOrDefault();
}
diff --git a/wwwroot/js/globe.js b/wwwroot/js/globe.js
index 056d36b..16a1f00 100644
--- a/wwwroot/js/globe.js
+++ b/wwwroot/js/globe.js
@@ -198,6 +198,15 @@ function resize() {
state.globe.width(width).height(height)
pause(document.visibilityState !== "visible")
wake()
+ // a new shape (the phone's banner opened full screen, or closed again) wants the camera at another height
+ const shape = narrowness()
+ if (Math.abs(shape - (state.shape ?? shape)) > 0.05) {
+ if (state.view)
+ scheduleView()
+ else
+ state.globe.pointOfView({ ...state.globe.pointOfView(), altitude: 2.6 * shape }, reducedMotion.matches ? 0 : 600)
+ }
+ state.shape = shape
}
async function build(width, height) {
diff --git a/wwwroot/main.js b/wwwroot/main.js
index d729673..ae077be 100644
--- a/wwwroot/main.js
+++ b/wwwroot/main.js
@@ -123,9 +123,10 @@ window.clearAppBadge = () => {
window.logFromJs(e.message, "clearAppBadge")
}
}
+// a phone: narrower than Tailwind's md, or held sideways (as short as the short: variant)
window.isMobileMedia = () => {
try {
- return ((window.innerWidth > 0) ? window.innerWidth : screen.width) <= 768
+ return window.matchMedia("(max-width: 767.98px), (max-height: 30rem)").matches
} catch (e) {
console.error(e)
window.logFromJs(e.message, "isMobileMedia")