Archived
fix: keep PWA chrome visible after resume
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@jmfederico/pi-web": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Keep PWA navigation bars visible after returning to the app from the background.
|
||||||
@@ -51,6 +51,7 @@ const THEME_AUTO_OFF_VALUE = "auto:off";
|
|||||||
const THEME_OPTION_PREFIX = "theme:";
|
const THEME_OPTION_PREFIX = "theme:";
|
||||||
const TERMINAL_ROUTE_NAMESPACE = queryNamespace("core:workspace.terminal");
|
const TERMINAL_ROUTE_NAMESPACE = queryNamespace("core:workspace.terminal");
|
||||||
const REFRESH_LONG_PRESS_MS = 550;
|
const REFRESH_LONG_PRESS_MS = 550;
|
||||||
|
const VIEWPORT_POSITION_REPAIR_DELAY_MS = 250;
|
||||||
|
|
||||||
@customElement("pi-web-app")
|
@customElement("pi-web-app")
|
||||||
export class PiWebApp extends LitElement {
|
export class PiWebApp extends LitElement {
|
||||||
@@ -119,6 +120,8 @@ export class PiWebApp extends LitElement {
|
|||||||
private themePreference: ThemePreference = readStoredThemePreference() ?? DEFAULT_THEME_PREFERENCE;
|
private themePreference: ThemePreference = readStoredThemePreference() ?? DEFAULT_THEME_PREFERENCE;
|
||||||
private refreshLongPressTimer: number | undefined;
|
private refreshLongPressTimer: number | undefined;
|
||||||
private suppressNextRefreshClick = false;
|
private suppressNextRefreshClick = false;
|
||||||
|
private viewportPositionRepairFrame: number | undefined;
|
||||||
|
private viewportPositionRepairTimer: number | undefined;
|
||||||
@state() private activeThemeId: QualifiedContributionId = CLASSIC_THEME_ID;
|
@state() private activeThemeId: QualifiedContributionId = CLASSIC_THEME_ID;
|
||||||
@state() private isMobileNavigationLayout = this.mobileNavigationMedia?.matches ?? false;
|
@state() private isMobileNavigationLayout = this.mobileNavigationMedia?.matches ?? false;
|
||||||
@state() private isPwaDisplayMode = detectPwaDisplayMode(this.pwaDisplayModeMedia);
|
@state() private isPwaDisplayMode = detectPwaDisplayMode(this.pwaDisplayModeMedia);
|
||||||
@@ -131,7 +134,11 @@ export class PiWebApp extends LitElement {
|
|||||||
@state() private mobileTabsCanScrollLeft = false;
|
@state() private mobileTabsCanScrollLeft = false;
|
||||||
@state() private mobileTabsCanScrollRight = false;
|
@state() private mobileTabsCanScrollRight = false;
|
||||||
private readonly onPopState = () => void this.withChatScrollTransition(() => this.restoreRoute(false));
|
private readonly onPopState = () => void this.withChatScrollTransition(() => this.restoreRoute(false));
|
||||||
|
private readonly onPageShow = () => {
|
||||||
|
this.repairViewportPosition();
|
||||||
|
};
|
||||||
private readonly onFocus = () => {
|
private readonly onFocus = () => {
|
||||||
|
this.repairViewportPosition();
|
||||||
void this.sessions.refreshSelectedSession();
|
void this.sessions.refreshSelectedSession();
|
||||||
void this.refreshPiWebStatus();
|
void this.refreshPiWebStatus();
|
||||||
void this.refreshWorkspaceActivity();
|
void this.refreshWorkspaceActivity();
|
||||||
@@ -139,6 +146,7 @@ export class PiWebApp extends LitElement {
|
|||||||
};
|
};
|
||||||
private readonly onVisibilityChange = () => {
|
private readonly onVisibilityChange = () => {
|
||||||
if (document.visibilityState === "visible") {
|
if (document.visibilityState === "visible") {
|
||||||
|
this.repairViewportPosition();
|
||||||
void this.sessions.refreshSelectedSession();
|
void this.sessions.refreshSelectedSession();
|
||||||
void this.refreshPiWebStatus();
|
void this.refreshPiWebStatus();
|
||||||
void this.refreshWorkspaceActivity();
|
void this.refreshWorkspaceActivity();
|
||||||
@@ -185,6 +193,7 @@ export class PiWebApp extends LitElement {
|
|||||||
override connectedCallback(): void {
|
override connectedCallback(): void {
|
||||||
super.connectedCallback();
|
super.connectedCallback();
|
||||||
window.addEventListener("popstate", this.onPopState);
|
window.addEventListener("popstate", this.onPopState);
|
||||||
|
window.addEventListener("pageshow", this.onPageShow);
|
||||||
window.addEventListener("focus", this.onFocus);
|
window.addEventListener("focus", this.onFocus);
|
||||||
document.addEventListener("click", this.onDocumentClick);
|
document.addEventListener("click", this.onDocumentClick);
|
||||||
document.addEventListener("visibilitychange", this.onVisibilityChange);
|
document.addEventListener("visibilitychange", this.onVisibilityChange);
|
||||||
@@ -203,6 +212,7 @@ export class PiWebApp extends LitElement {
|
|||||||
|
|
||||||
override disconnectedCallback(): void {
|
override disconnectedCallback(): void {
|
||||||
window.removeEventListener("popstate", this.onPopState);
|
window.removeEventListener("popstate", this.onPopState);
|
||||||
|
window.removeEventListener("pageshow", this.onPageShow);
|
||||||
window.removeEventListener("focus", this.onFocus);
|
window.removeEventListener("focus", this.onFocus);
|
||||||
document.removeEventListener("click", this.onDocumentClick);
|
document.removeEventListener("click", this.onDocumentClick);
|
||||||
document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
document.removeEventListener("visibilitychange", this.onVisibilityChange);
|
||||||
@@ -226,6 +236,7 @@ export class PiWebApp extends LitElement {
|
|||||||
this.mobileTabsResizeObserver = undefined;
|
this.mobileTabsResizeObserver = undefined;
|
||||||
this.observedMobileTabs = undefined;
|
this.observedMobileTabs = undefined;
|
||||||
this.clearRefreshLongPressTimer();
|
this.clearRefreshLongPressTimer();
|
||||||
|
this.clearViewportPositionRepair();
|
||||||
super.disconnectedCallback();
|
super.disconnectedCallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -353,7 +364,51 @@ export class PiWebApp extends LitElement {
|
|||||||
await this.chatView?.updateComplete;
|
await this.chatView?.updateComplete;
|
||||||
await nextFrame();
|
await nextFrame();
|
||||||
this.chatView?.restoreScrollPosition();
|
this.chatView?.restoreScrollPosition();
|
||||||
this.promptEditor?.focusInput();
|
if (this.shouldAutoFocusPrompt()) this.promptEditor?.focusInput();
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldAutoFocusPrompt(): boolean {
|
||||||
|
return !this.isMobileNavigationLayout && !this.isPwaDisplayMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private repairViewportPosition(): void {
|
||||||
|
if (!this.shouldRepairViewportPosition()) return;
|
||||||
|
this.resetViewportScroll();
|
||||||
|
if (this.viewportPositionRepairFrame !== undefined) window.cancelAnimationFrame(this.viewportPositionRepairFrame);
|
||||||
|
this.viewportPositionRepairFrame = window.requestAnimationFrame(() => {
|
||||||
|
this.viewportPositionRepairFrame = undefined;
|
||||||
|
this.resetViewportScroll();
|
||||||
|
this.viewportPositionRepairFrame = window.requestAnimationFrame(() => {
|
||||||
|
this.viewportPositionRepairFrame = undefined;
|
||||||
|
this.resetViewportScroll();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (this.viewportPositionRepairTimer !== undefined) window.clearTimeout(this.viewportPositionRepairTimer);
|
||||||
|
this.viewportPositionRepairTimer = window.setTimeout(() => {
|
||||||
|
this.viewportPositionRepairTimer = undefined;
|
||||||
|
this.resetViewportScroll();
|
||||||
|
}, VIEWPORT_POSITION_REPAIR_DELAY_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldRepairViewportPosition(): boolean {
|
||||||
|
return this.isMobileNavigationLayout || this.isPwaDisplayMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetViewportScroll(): void {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
document.documentElement.scrollTop = 0;
|
||||||
|
document.body.scrollTop = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearViewportPositionRepair(): void {
|
||||||
|
if (this.viewportPositionRepairFrame !== undefined) {
|
||||||
|
window.cancelAnimationFrame(this.viewportPositionRepairFrame);
|
||||||
|
this.viewportPositionRepairFrame = undefined;
|
||||||
|
}
|
||||||
|
if (this.viewportPositionRepairTimer !== undefined) {
|
||||||
|
window.clearTimeout(this.viewportPositionRepairTimer);
|
||||||
|
this.viewportPositionRepairTimer = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async withChatPrependTransition(action: () => Promise<void>) {
|
private async withChatPrependTransition(action: () => Promise<void>) {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export interface CompletionItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const appStyles = css`
|
export const appStyles = css`
|
||||||
:host { display: block; height: 100dvh; box-sizing: border-box; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); color: var(--pi-text); background: var(--pi-bg); font: 14px system-ui, sans-serif; }
|
:host { position: fixed; top: 0; right: 0; left: 0; display: block; height: 100dvh; box-sizing: border-box; overflow: hidden; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); color: var(--pi-text); background: var(--pi-bg); font: 14px system-ui, sans-serif; }
|
||||||
.shell { display: grid; grid-template-columns: 340px minmax(420px, 1fr) minmax(360px, 42vw); height: 100%; min-height: 0; }
|
.shell { display: grid; grid-template-columns: 340px minmax(420px, 1fr) minmax(360px, 42vw); height: 100%; min-height: 0; }
|
||||||
aside { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--pi-border); overflow: hidden; }
|
aside { display: flex; flex-direction: column; min-height: 0; border-right: 1px solid var(--pi-border); overflow: hidden; }
|
||||||
header { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px; border-bottom: 1px solid var(--pi-border); }
|
header { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 12px; border-bottom: 1px solid var(--pi-border); }
|
||||||
|
|||||||
Reference in New Issue
Block a user