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'; import { CapabilitiesProvider } from '../../hooks/useCapabilities'; import { MediaConfigProvider } from '../../hooks/useMediaConfig'; import { MatrixClientProvider } from '../../hooks/useMatrixClient'; import { SpecVersions } from './SpecVersions'; import Windows from '../../organisms/pw/Windows'; import Dialogs from '../../organisms/pw/Dialogs'; import ReusableContextMenu from '../../atoms/context-menu/ReusableContextMenu'; import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback'; import { useSyncState } from '../../hooks/useSyncState'; import { stopPropagation } from '../../utils/keyboard'; import { SyncStatus } from './SyncStatus'; function ClientRootLoading() { return ( Heating up ); } function ClientRootOptions({ mx }: { mx: MatrixClient }) { const [menuAnchor, setMenuAnchor] = useState(); const handleToggle: MouseEventHandler = (evt) => { const cords = evt.currentTarget.getBoundingClientRect(); setMenuAnchor((currentState) => { if (currentState) return undefined; return cords; }); }; return ( setMenuAnchor(undefined), clickOutsideDeactivates: true, isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown', isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp', escapeDeactivates: stopPropagation, }} > clearCacheAndReload(mx)} size="300" radii="300"> Clear Cache and Reload logoutClient(mx)} size="300" radii="300" variant="Critical" fill="None" > Logout } /> ); } 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; }; export function ClientRoot({ children }: ClientRootProps) { const [loading, setLoading] = useState(true); const { baseUrl } = getSecret(); const [loadState, loadMatrix] = useAsyncCallback( useCallback(() => initClient(getSecret() as any), []) ); const mx = loadState.status === AsyncStatus.Success ? loadState.data : undefined; const [startState, startMatrix] = useAsyncCallback( useCallback((m) => startClient(m), []) ); useLogoutListener(mx); useEffect(() => { 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 ( {mx && } {loading && mx && } {(loadState.status === AsyncStatus.Error || startState.status === AsyncStatus.Error) && ( {loadState.status === AsyncStatus.Error && ( {`Failed to load. ${loadState.error.message}`} )} {startState.status === AsyncStatus.Error && ( {`Failed to load. ${startState.error.message}`} )} )} {loading || !mx ? ( ) : ( {(capabilities, mediaConfig) => ( {children} )} )} ); }