-
Matiss Janis Aboltins authoredMatiss Janis Aboltins authored
App.tsx 5.18 KiB
import React, { useEffect, useState } from 'react';
import {
ErrorBoundary,
useErrorBoundary,
type FallbackProps,
} from 'react-error-boundary';
import { useSelector } from 'react-redux';
import * as Platform from 'loot-core/src/client/platform';
import {
init as initConnection,
send,
} from 'loot-core/src/platform/client/fetch';
import { type GlobalPrefs } from 'loot-core/src/types/prefs';
import { useActions } from '../hooks/useActions';
import installPolyfills from '../polyfills';
import { ResponsiveProvider } from '../ResponsiveProvider';
import { styles, hasHiddenScrollbars, ThemeStyle } from '../style';
import AppBackground from './AppBackground';
import View from './common/View';
import DevelopmentTopBar from './DevelopmentTopBar';
import FatalError from './FatalError';
import FinancesApp from './FinancesApp';
import ManagementApp from './manager/ManagementApp';
import MobileWebMessage from './MobileWebMessage';
import UpdateNotification from './UpdateNotification';
type AppProps = {
budgetId: string;
cloudFileId: string;
loadingText: string;
loadBudget: (
id: string,
loadingText?: string,
options?: object,
) => Promise<void>;
closeBudget: () => Promise<void>;
loadGlobalPrefs: () => Promise<GlobalPrefs>;
};
function App({
budgetId,
cloudFileId,
loadingText,
loadBudget,
closeBudget,
loadGlobalPrefs,
}: AppProps) {
const [initializing, setInitializing] = useState(true);
const { showBoundary: showErrorBoundary } = useErrorBoundary();
async function init() {
const socketName = await global.Actual.getServerSocket();
await initConnection(socketName);
// Load any global prefs
await loadGlobalPrefs();
// Open the last opened budget, if any
const budgetId = await send('get-last-opened-backup');
if (budgetId) {
await loadBudget(budgetId);
// Check to see if this file has been remotely deleted (but
// don't block on this in case they are offline or something)
send('get-remote-files').then(files => {
if (files) {
let remoteFile = files.find(f => f.fileId === cloudFileId);
if (remoteFile && remoteFile.deleted) {
closeBudget();
}
}
});
}
}
useEffect(() => {
async function initAll() {
await Promise.all([installPolyfills(), init()]);
setInitializing(false);
}
initAll().catch(showErrorBoundary);
}, []);
useEffect(() => {
global.Actual.updateAppMenu(!!budgetId);
}, [budgetId]);
return (
<>
{initializing ? (
<AppBackground initializing={initializing} loadingText={loadingText} />
) : budgetId ? (
<FinancesApp />
) : (
<>
<AppBackground
initializing={initializing}
loadingText={loadingText}
/>
<ManagementApp isLoading={loadingText != null} />
</>
)}
<UpdateNotification />
<MobileWebMessage />
</>
);
}
function ErrorFallback({ error }: FallbackProps) {
return (
<>
<AppBackground />
<FatalError error={error} buttonText="Restart app" />
</>
);
}
function AppWrapper() {
let budgetId = useSelector(
state => state.prefs.local && state.prefs.local.id,
);
let cloudFileId = useSelector(
state => state.prefs.local && state.prefs.local.cloudFileId,
);
let loadingText = useSelector(state => state.app.loadingText);
let { loadBudget, closeBudget, loadGlobalPrefs, sync } = useActions();
const [hiddenScrollbars, setHiddenScrollbars] = useState(
hasHiddenScrollbars(),
);
useEffect(() => {
function checkScrollbars() {
if (hiddenScrollbars !== hasHiddenScrollbars()) {
setHiddenScrollbars(hasHiddenScrollbars());
}
}
let isSyncing = false;
async function onVisibilityChange() {
if (!isSyncing) {
console.debug('triggering sync because of visibility change');
isSyncing = true;
await sync();
isSyncing = false;
}
}
window.addEventListener('focus', checkScrollbars);
window.addEventListener('visibilitychange', onVisibilityChange);
return () => {
window.removeEventListener('focus', checkScrollbars);
window.removeEventListener('visibilitychange', onVisibilityChange);
};
}, [sync]);
return (
<ResponsiveProvider>
<View
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
>
<View
key={hiddenScrollbars ? 'hidden-scrollbars' : 'scrollbars'}
style={{
flexGrow: 1,
overflow: 'hidden',
...styles.lightScrollbar,
}}
>
<ErrorBoundary FallbackComponent={ErrorFallback}>
{process.env.REACT_APP_REVIEW_ID && !Platform.isPlaywright && (
<DevelopmentTopBar />
)}
<App
budgetId={budgetId}
cloudFileId={cloudFileId}
loadingText={loadingText}
loadBudget={loadBudget}
closeBudget={closeBudget}
loadGlobalPrefs={loadGlobalPrefs}
/>
</ErrorBoundary>
<ThemeStyle />
</View>
</View>
</ResponsiveProvider>
);
}
export default AppWrapper;