mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-05 23:10:28 +03:00
handle error in loading screen (#1823)
* handle client boot error in loading screen * use sync state hook in client root * add loading screen options * removed extra condition in loading finish * add sync connection status bar
This commit is contained in:
parent
e046c59f7c
commit
e2228a18c1
62 changed files with 609 additions and 510 deletions
|
|
@ -1,6 +1,27 @@
|
|||
import { Box, Spinner, Text } from 'folds';
|
||||
import React, { ReactNode, useEffect, useState } from 'react';
|
||||
import initMatrix from '../../../client/initMatrix';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
config,
|
||||
Dialog,
|
||||
Icon,
|
||||
IconButton,
|
||||
Icons,
|
||||
Menu,
|
||||
MenuItem,
|
||||
PopOut,
|
||||
RectCords,
|
||||
Spinner,
|
||||
Text,
|
||||
} from 'folds';
|
||||
import { HttpApiEvent, HttpApiEventHandlerMap, MatrixClient } from 'matrix-js-sdk';
|
||||
import FocusTrap from 'focus-trap-react';
|
||||
import React, { MouseEventHandler, ReactNode, useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
clearCacheAndReload,
|
||||
initClient,
|
||||
logoutClient,
|
||||
startClient,
|
||||
} from '../../../client/initMatrix';
|
||||
import { getSecret } from '../../../client/state/auth';
|
||||
import { SplashScreen } from '../../components/splash-screen';
|
||||
import { CapabilitiesAndMediaConfigLoader } from '../../components/CapabilitiesAndMediaConfigLoader';
|
||||
|
|
@ -13,6 +34,10 @@ import Dialogs from '../../organisms/pw/Dialogs';
|
|||
import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu';
|
||||
import { useSetting } from '../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../state/settings';
|
||||
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
|
||||
import { useSyncState } from '../../hooks/useSyncState';
|
||||
import { stopPropagation } from '../../utils/keyboard';
|
||||
import { SyncStatus } from './SyncStatus';
|
||||
|
||||
function SystemEmojiFeature() {
|
||||
const [twitterEmoji] = useSetting(settingsAtom, 'twitterEmoji');
|
||||
|
|
@ -37,6 +62,89 @@ function ClientRootLoading() {
|
|||
);
|
||||
}
|
||||
|
||||
function ClientRootOptions({ mx }: { mx: MatrixClient }) {
|
||||
const [menuAnchor, setMenuAnchor] = useState<RectCords>();
|
||||
|
||||
const handleToggle: MouseEventHandler<HTMLButtonElement> = (evt) => {
|
||||
const cords = evt.currentTarget.getBoundingClientRect();
|
||||
setMenuAnchor((currentState) => {
|
||||
if (currentState) return undefined;
|
||||
return cords;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: config.space.S100,
|
||||
right: config.space.S100,
|
||||
}}
|
||||
variant="Background"
|
||||
fill="None"
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<Icon size="200" src={Icons.VerticalDots} />
|
||||
<PopOut
|
||||
anchor={menuAnchor}
|
||||
position="Bottom"
|
||||
align="End"
|
||||
offset={6}
|
||||
content={
|
||||
<FocusTrap
|
||||
focusTrapOptions={{
|
||||
initialFocus: false,
|
||||
returnFocusOnDeactivate: false,
|
||||
onDeactivate: () => setMenuAnchor(undefined),
|
||||
clickOutsideDeactivates: true,
|
||||
isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
|
||||
isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
|
||||
escapeDeactivates: stopPropagation,
|
||||
}}
|
||||
>
|
||||
<Menu>
|
||||
<Box direction="Column" gap="100" style={{ padding: config.space.S100 }}>
|
||||
<MenuItem onClick={() => clearCacheAndReload(mx)} size="300" radii="300">
|
||||
<Text as="span" size="T300" truncate>
|
||||
Clear Cache and Reload
|
||||
</Text>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => logoutClient(mx)}
|
||||
size="300"
|
||||
radii="300"
|
||||
variant="Critical"
|
||||
fill="None"
|
||||
>
|
||||
<Text as="span" size="T300" truncate>
|
||||
Logout
|
||||
</Text>
|
||||
</MenuItem>
|
||||
</Box>
|
||||
</Menu>
|
||||
</FocusTrap>
|
||||
}
|
||||
/>
|
||||
</IconButton>
|
||||
);
|
||||
}
|
||||
|
||||
const useLogoutListener = (mx?: MatrixClient) => {
|
||||
useEffect(() => {
|
||||
const handleLogout: HttpApiEventHandlerMap[HttpApiEvent.SessionLoggedOut] = async () => {
|
||||
mx?.stopClient();
|
||||
await mx?.clearStores();
|
||||
window.localStorage.clear();
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
mx?.on(HttpApiEvent.SessionLoggedOut, handleLogout);
|
||||
return () => {
|
||||
mx?.removeListener(HttpApiEvent.SessionLoggedOut, handleLogout);
|
||||
};
|
||||
}, [mx]);
|
||||
};
|
||||
|
||||
type ClientRootProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
|
|
@ -44,30 +152,71 @@ export function ClientRoot({ children }: ClientRootProps) {
|
|||
const [loading, setLoading] = useState(true);
|
||||
const { baseUrl } = getSecret();
|
||||
|
||||
const [loadState, loadMatrix] = useAsyncCallback<MatrixClient, Error, []>(
|
||||
useCallback(() => initClient(getSecret() as any), [])
|
||||
);
|
||||
const mx = loadState.status === AsyncStatus.Success ? loadState.data : undefined;
|
||||
const [startState, startMatrix] = useAsyncCallback<void, Error, [MatrixClient]>(
|
||||
useCallback((m) => startClient(m), [])
|
||||
);
|
||||
|
||||
useLogoutListener(mx);
|
||||
|
||||
useEffect(() => {
|
||||
const handleStart = () => {
|
||||
setLoading(false);
|
||||
};
|
||||
initMatrix.once('init_loading_finished', handleStart);
|
||||
if (!initMatrix.matrixClient) initMatrix.init();
|
||||
return () => {
|
||||
initMatrix.removeListener('init_loading_finished', handleStart);
|
||||
};
|
||||
}, []);
|
||||
if (loadState.status === AsyncStatus.Idle) {
|
||||
loadMatrix();
|
||||
}
|
||||
}, [loadState, loadMatrix]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mx && !mx.clientRunning) {
|
||||
startMatrix(mx);
|
||||
}
|
||||
}, [mx, startMatrix]);
|
||||
|
||||
useSyncState(
|
||||
mx,
|
||||
useCallback((state) => {
|
||||
if (state === 'PREPARED') {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [])
|
||||
);
|
||||
|
||||
return (
|
||||
<SpecVersions baseUrl={baseUrl!}>
|
||||
{loading ? (
|
||||
{mx && <SyncStatus mx={mx} />}
|
||||
{loading && mx && <ClientRootOptions mx={mx} />}
|
||||
{(loadState.status === AsyncStatus.Error || startState.status === AsyncStatus.Error) && (
|
||||
<SplashScreen>
|
||||
<Box direction="Column" grow="Yes" alignItems="Center" justifyContent="Center" gap="400">
|
||||
<Dialog>
|
||||
<Box direction="Column" gap="400" style={{ padding: config.space.S400 }}>
|
||||
{loadState.status === AsyncStatus.Error && (
|
||||
<Text>{`Failed to load. ${loadState.error.message}`}</Text>
|
||||
)}
|
||||
{startState.status === AsyncStatus.Error && (
|
||||
<Text>{`Failed to load. ${startState.error.message}`}</Text>
|
||||
)}
|
||||
<Button variant="Critical" onClick={loadMatrix}>
|
||||
<Text as="span" size="B400">
|
||||
Retry
|
||||
</Text>
|
||||
</Button>
|
||||
</Box>
|
||||
</Dialog>
|
||||
</Box>
|
||||
</SplashScreen>
|
||||
)}
|
||||
{loading || !mx ? (
|
||||
<ClientRootLoading />
|
||||
) : (
|
||||
<MatrixClientProvider value={initMatrix.matrixClient!}>
|
||||
<MatrixClientProvider value={mx}>
|
||||
<CapabilitiesAndMediaConfigLoader>
|
||||
{(capabilities, mediaConfig) => (
|
||||
<CapabilitiesProvider value={capabilities ?? {}}>
|
||||
<MediaConfigProvider value={mediaConfig ?? {}}>
|
||||
{children}
|
||||
|
||||
{/* TODO: remove these components after navigation refactor */}
|
||||
<Windows />
|
||||
<Dialogs />
|
||||
<ReusableContextMenu />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue