Fix Android updates dialog, mobile About page, and user color crash guard.
All checks were successful
Trigger cinny-mobile / dispatch (push) Successful in 2s

Use stripBase for update static copy paths, show Settings detail on mobile
when nav is hidden, pause Settings focus trap during release notes, and guard
extractMemberColorPreference when room is not a Matrix Room instance.
This commit is contained in:
2026-08-23 16:18:35 +10:00
parent c264de53b5
commit 7d866404b3
7 changed files with 23 additions and 6 deletions

View File

@@ -96,6 +96,12 @@
</script> </script>
<div id="root"></div> <div id="root"></div>
<div id="portalContainer"></div> <div id="portalContainer"></div>
<style>
#portalContainer {
position: relative;
z-index: 10000;
}
</style>
<script type="module" src="./src/index.tsx"></script> <script type="module" src="./src/index.tsx"></script>
</body> </body>
</html> </html>

View File

@@ -1,7 +1,9 @@
import React, { ReactNode, useCallback, useState } from 'react'; import React, { ReactNode, useCallback, useState } from 'react';
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import { useAtomValue } from 'jotai';
import { Modal, Overlay, OverlayBackdrop, OverlayCenter, PopOutContainerProvider } from 'folds'; import { Modal, Overlay, OverlayBackdrop, OverlayCenter, PopOutContainerProvider } from 'folds';
import { stopPropagation } from '../utils/keyboard'; import { stopPropagation } from '../utils/keyboard';
import { releaseNotesDialogAtom } from '../state/releaseNotes';
type Modal500Props = { type Modal500Props = {
requestClose: () => void; requestClose: () => void;
@@ -10,11 +12,13 @@ type Modal500Props = {
export function Modal500({ requestClose, children }: Modal500Props) { export function Modal500({ requestClose, children }: Modal500Props) {
const [modalEl, setModalEl] = useState<HTMLDivElement | null>(null); const [modalEl, setModalEl] = useState<HTMLDivElement | null>(null);
const modalRef = useCallback((el: HTMLDivElement | null) => setModalEl(el), []); const modalRef = useCallback((el: HTMLDivElement | null) => setModalEl(el), []);
const releaseNotesOpen = useAtomValue(releaseNotesDialogAtom).open;
return ( return (
<Overlay open backdrop={<OverlayBackdrop />}> <Overlay open backdrop={<OverlayBackdrop />}>
<OverlayCenter> <OverlayCenter>
<FocusTrap <FocusTrap
active={!releaseNotesOpen}
focusTrapOptions={{ focusTrapOptions={{
initialFocus: false, initialFocus: false,
clickOutsideDeactivates: true, clickOutsideDeactivates: true,

View File

@@ -16,6 +16,7 @@ type PageRootProps = {
export function PageRoot({ nav, children }: PageRootProps) { export function PageRoot({ nav, children }: PageRootProps) {
const screenSize = useScreenSizeContext(); const screenSize = useScreenSizeContext();
const showCompactMaster = useShowCompactMasterView(); const showCompactMaster = useShowCompactMasterView();
const showDetail = !showCompactMaster || nav == null;
return ( return (
<Box grow="Yes" className={ContainerColor({ variant: 'Background' })}> <Box grow="Yes" className={ContainerColor({ variant: 'Background' })}>
@@ -23,7 +24,7 @@ export function PageRoot({ nav, children }: PageRootProps) {
{screenSize !== ScreenSize.Mobile && ( {screenSize !== ScreenSize.Mobile && (
<Line variant="Background" size="300" direction="Vertical" /> <Line variant="Background" size="300" direction="Vertical" />
)} )}
{!showCompactMaster && children} {showDetail && children}
</Box> </Box>
); );
} }

View File

@@ -12,6 +12,7 @@ import {
Scroll, Scroll,
Spinner, Spinner,
Text, Text,
usePopOutContainer,
} from 'folds'; } from 'folds';
import { Icon, Icons } from '../icons'; import { Icon, Icons } from '../icons';
import { stopPropagation } from '../../utils/keyboard'; import { stopPropagation } from '../../utils/keyboard';
@@ -36,6 +37,7 @@ export function UpdatesDialog({
onClose, onClose,
}: UpdatesDialogProps) { }: UpdatesDialogProps) {
const [showOlderList, setShowOlderList] = useState(false); const [showOlderList, setShowOlderList] = useState(false);
const popOutContainer = usePopOutContainer();
const displayVersion = activeDoc?.version; const displayVersion = activeDoc?.version;
const displayTitle = activeDoc?.title ?? (displayVersion ? `Version ${displayVersion}` : 'Release notes'); const displayTitle = activeDoc?.title ?? (displayVersion ? `Version ${displayVersion}` : 'Release notes');
@@ -48,7 +50,9 @@ export function UpdatesDialog({
}; };
const portalTarget = const portalTarget =
document.getElementById('portalContainer') ?? document.body; popOutContainer ??
document.getElementById('portalContainer') ??
document.body;
return createPortal( return createPortal(
<div className={css.PortalLayer}> <div className={css.PortalLayer}>

View File

@@ -21,6 +21,9 @@ export function About({ requestClose }: AboutProps) {
const [version, setVersion] = useState<string>(''); const [version, setVersion] = useState<string>('');
const [protocolStatus, setProtocolStatus] = useState<string>('Checking desktop protocol integration...'); const [protocolStatus, setProtocolStatus] = useState<string>('Checking desktop protocol integration...');
const [protocolBusy, setProtocolBusy] = useState<boolean>(false); const [protocolBusy, setProtocolBusy] = useState<boolean>(false);
const [updatePreview, setUpdatePreview] = useState<{ title: string; description: string } | null>(
null
);
const formatProtocolStatus = useCallback((data: { const formatProtocolStatus = useCallback((data: {
scheme: string; scheme: string;
@@ -101,8 +104,6 @@ export function About({ requestClose }: AboutProps) {
getCurrentUpdatePreview().then(setUpdatePreview).catch(() => setUpdatePreview(null)); getCurrentUpdatePreview().then(setUpdatePreview).catch(() => setUpdatePreview(null));
}, [refreshProtocolStatus]); }, [refreshProtocolStatus]);
const [updatePreview, setUpdatePreview] = useState<{ title: string; description: string } | null>(null);
return ( return (
<Page> <Page>
<PageHeader outlined={false}> <PageHeader outlined={false}>

View File

@@ -152,7 +152,7 @@ export async function loadColorPreference(
} }
export function extractMemberColorPreference(room: Room | undefined, userId: string): ColorPreference | undefined { export function extractMemberColorPreference(room: Room | undefined, userId: string): ColorPreference | undefined {
if (!room) return undefined; if (!room || typeof (room as Room).getMember !== 'function' || !userId) return undefined;
const member = room.getMember(userId); const member = room.getMember(userId);
const content = member?.events.member?.getContent(); const content = member?.events.member?.getContent();
if (!content) return undefined; if (!content) return undefined;

View File

@@ -56,7 +56,8 @@ const copyFiles = {
{ {
src: 'public/update/**/*', src: 'public/update/**/*',
dest: 'update', dest: 'update',
rename: (_name, _ext, fullPath) => fullPath.replace(/^public[/\\]update[/\\]/, ''), // stripBase removes public/update from the matched path; rename alone only changes the filename.
rename: { stripBase: 2 },
}, },
], ],
}; };