Fix View updates on Android: bundled notes, portal stack, tap handling.
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 2s
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 2s
Bundle update markdown at build time for Capacitor, portal into portalContainer above Settings, defer open to avoid FocusTrap closing on the same tap, and disable click-outside dismiss.
This commit is contained in:
@@ -7,6 +7,7 @@ export const PortalLayer = style({
|
|||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
zIndex: config.zIndex.Max,
|
zIndex: config.zIndex.Max,
|
||||||
|
pointerEvents: 'auto',
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Dialog + overlay padding must never exceed the viewport height. */
|
/** Dialog + overlay padding must never exceed the viewport height. */
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import FocusTrap from 'focus-trap-react';
|
|||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
config,
|
|
||||||
Dialog,
|
Dialog,
|
||||||
IconButton,
|
IconButton,
|
||||||
Overlay,
|
Overlay,
|
||||||
@@ -48,6 +47,9 @@ export function UpdatesDialog({
|
|||||||
setShowOlderList(false);
|
setShowOlderList(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const portalTarget =
|
||||||
|
document.getElementById('portalContainer') ?? document.body;
|
||||||
|
|
||||||
return createPortal(
|
return createPortal(
|
||||||
<div className={css.PortalLayer}>
|
<div className={css.PortalLayer}>
|
||||||
<Overlay open backdrop={<OverlayBackdrop />}>
|
<Overlay open backdrop={<OverlayBackdrop />}>
|
||||||
@@ -55,8 +57,7 @@ export function UpdatesDialog({
|
|||||||
<FocusTrap
|
<FocusTrap
|
||||||
focusTrapOptions={{
|
focusTrapOptions={{
|
||||||
initialFocus: false,
|
initialFocus: false,
|
||||||
onDeactivate: onClose,
|
clickOutsideDeactivates: false,
|
||||||
clickOutsideDeactivates: true,
|
|
||||||
escapeDeactivates: stopPropagation,
|
escapeDeactivates: stopPropagation,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -181,6 +182,6 @@ export function UpdatesDialog({
|
|||||||
</OverlayCenter>
|
</OverlayCenter>
|
||||||
</Overlay>
|
</Overlay>
|
||||||
</div>,
|
</div>,
|
||||||
document.body
|
portalTarget
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ type UpdaterInfo = {
|
|||||||
export function useOpenReleaseNotesDialog(): () => void {
|
export function useOpenReleaseNotesDialog(): () => void {
|
||||||
const setDialogState = useSetAtom(releaseNotesDialogAtom);
|
const setDialogState = useSetAtom(releaseNotesDialogAtom);
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
setDialogState({ open: true, manual: true });
|
// Defer so the opening tap does not trip FocusTrap click-outside on Android.
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
setDialogState({ open: true, manual: true });
|
||||||
|
});
|
||||||
}, [setDialogState]);
|
}, [setDialogState]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
|
import bundledManifest from '../../../public/update/manifest.json';
|
||||||
import { trimTrailingSlash } from '../utils/common';
|
import { trimTrailingSlash } from '../utils/common';
|
||||||
|
|
||||||
|
const bundledMarkdownByFile = import.meta.glob('../../../public/update/*.md', {
|
||||||
|
query: '?raw',
|
||||||
|
import: 'default',
|
||||||
|
eager: true,
|
||||||
|
}) as Record<string, string>;
|
||||||
|
|
||||||
function getUpdateBaseUrl(): string {
|
function getUpdateBaseUrl(): string {
|
||||||
const basePath = trimTrailingSlash(import.meta.env.BASE_URL || './');
|
const basePath = trimTrailingSlash(import.meta.env.BASE_URL || './');
|
||||||
const relative =
|
const relative =
|
||||||
@@ -64,9 +71,26 @@ function parseUpdateMarkdown(file: string, source: string): ParsedUpdateDoc {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadBundledManifest(): UpdateManifest {
|
||||||
|
const manifest = bundledManifest as UpdateManifest;
|
||||||
|
return {
|
||||||
|
current: manifest.current,
|
||||||
|
older: (manifest.older ?? []).filter((entry) => entry?.file),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadBundledDocument(file: string): ParsedUpdateDoc | null {
|
||||||
|
const safeFile = file.replace(/^\/+/, '');
|
||||||
|
const entry = Object.entries(bundledMarkdownByFile).find(([path]) =>
|
||||||
|
path.endsWith(`/${safeFile}`)
|
||||||
|
);
|
||||||
|
if (!entry) return null;
|
||||||
|
return parseUpdateMarkdown(safeFile, entry[1]);
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveUpdateAssetUrl(src: string | undefined): string | undefined {
|
export function resolveUpdateAssetUrl(src: string | undefined): string | undefined {
|
||||||
if (!src) return undefined;
|
if (!src) return undefined;
|
||||||
if (/^(https?:|data:|blob:)/i.test(src)) return src;
|
if (/^(https?:|data:|blob:|capacitor:)/i.test(src)) return src;
|
||||||
if (src.startsWith('/')) return src;
|
if (src.startsWith('/')) return src;
|
||||||
|
|
||||||
const normalized = src.replace(/^\.\//, '');
|
const normalized = src.replace(/^\.\//, '');
|
||||||
@@ -76,15 +100,21 @@ export function resolveUpdateAssetUrl(src: string | undefined): string | undefin
|
|||||||
export async function loadUpdateManifest(): Promise<UpdateManifest | null> {
|
export async function loadUpdateManifest(): Promise<UpdateManifest | null> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(getUpdateFileUrl('manifest.json'), { cache: 'no-cache' });
|
const response = await fetch(getUpdateFileUrl('manifest.json'), { cache: 'no-cache' });
|
||||||
if (!response.ok) return null;
|
if (response.ok) {
|
||||||
|
const data = (await response.json()) as UpdateManifest;
|
||||||
|
if (data?.current && Array.isArray(data.older)) {
|
||||||
|
return {
|
||||||
|
current: data.current,
|
||||||
|
older: data.older.filter((entry) => entry?.file),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// fall through to bundled copy (Capacitor / offline)
|
||||||
|
}
|
||||||
|
|
||||||
const data = (await response.json()) as UpdateManifest;
|
try {
|
||||||
if (!data?.current || !Array.isArray(data.older)) return null;
|
return loadBundledManifest();
|
||||||
|
|
||||||
return {
|
|
||||||
current: data.current,
|
|
||||||
older: data.older.filter((entry) => entry?.file),
|
|
||||||
};
|
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -96,13 +126,15 @@ export async function loadUpdateDocument(file: string): Promise<ParsedUpdateDoc
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(getUpdateFileUrl(safeFile), { cache: 'no-cache' });
|
const response = await fetch(getUpdateFileUrl(safeFile), { cache: 'no-cache' });
|
||||||
if (!response.ok) return null;
|
if (response.ok) {
|
||||||
|
const source = await response.text();
|
||||||
const source = await response.text();
|
return parseUpdateMarkdown(safeFile, source);
|
||||||
return parseUpdateMarkdown(safeFile, source);
|
}
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
// fall through to bundled copy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return loadBundledDocument(safeFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadCurrentUpdateDocument(): Promise<ParsedUpdateDoc | null> {
|
export async function loadCurrentUpdateDocument(): Promise<ParsedUpdateDoc | null> {
|
||||||
|
|||||||
Reference in New Issue
Block a user