Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | 608x 608x 1306x 1300x 6781x 1300x 608x 680x 680x 656x 1300x 1290x 671x 619x 454x 1300x 1x 1x 625x 1x 625x | import { ReactElement, Suspense, useEffect, useState } from "react";
import { BrowserRouter } from "react-router";
import styled from "styled-components";
import { config } from "shared/config";
import { AppRoutes } from "client/app/AppRoutes";
import { HistoryProvider } from "client/app/HistoryProvider";
import { prefetchLazyViews } from "client/app/lazyViews";
import { AdminMessageBanner } from "client/components/AdminMessageBanner";
import { Announcement } from "client/components/Announcement";
import { AppUpdateBanner } from "client/components/AppUpdateBanner";
import { ErrorBar } from "client/components/ErrorBar";
import { FirstLogin } from "client/components/FirstLogin";
import { HEADER_HEIGHT, Header } from "client/components/Header";
import { Loading } from "client/components/Loading";
import { ViewErrorBoundary } from "client/components/ViewErrorBoundary";
import { MOBILE_MARGIN } from "client/globalStyle";
import { TestGenerateSerial } from "client/test/test-components/TestGenerateSerial";
import { TestTime } from "client/test/test-components/TestTime";
import { startDataPolling } from "client/utils/dataPolling";
import { useAppSelector } from "client/utils/hooks";
import { getIconLibrary } from "client/utils/icons";
import { whenIdle } from "client/utils/whenIdle";
import { NotificationBar } from "client/views/event-log/NotificationBar";
const { loadedSettings, showTestValues, showAnnouncement } = config.client();
const App = (): ReactElement => {
const [loading, setLoading] = useState<boolean>(true);
const [dataLoaded, setDataLoaded] = useState<boolean>(false);
const userGroup = useAppSelector((state) => state.login.userGroup);
useEffect(() => {
return startDataPolling((succeeded) => {
setLoading(false);
if (succeeded) {
setDataLoaded(true);
}
});
}, []);
// Warm the split view chunks once a data load has actually succeeded and the
// browser reports spare time. Gating on success rather than on `loading`
// matters twice over: a failed load flips `loading` too, so these requests
// would pile onto a connection that is already struggling, and a failed
// dynamic import is cached by the browser for the session - the later real
// navigation would then reject instantly and force a full page reload even
// though connectivity had recovered. Re-runs when the role changes, because
// signing in as admin/helper makes chunks reachable that weren't before.
useEffect(() => {
if (!dataLoaded) {
return;
}
return whenIdle(() => {
prefetchLazyViews(userGroup);
});
}, [dataLoaded, userGroup]);
getIconLibrary();
return (
<>
{loading && <Loading />}
{!loading && (
<BrowserRouter>
<HistoryProvider>
{loadedSettings !== "production" && showTestValues && (
<TestValueContainer>
<TestTime />
<TestGenerateSerial />
</TestValueContainer>
)}
<Header />
{/* One sticky wrapper rather than sticky bars: siblings pinned to
the same offset would overlap instead of stacking */}
<StickyBars>
<FirstLogin />
<ErrorBar />
<AppUpdateBanner />
<AdminMessageBanner />
<NotificationBar />
</StickyBars>
{showAnnouncement && <Announcement />}
<AppContainer>
{/* Boundaries for the lazily loaded views, placed here rather
than around the whole app so a slow chunk or a render error in
one keeps the header and bars on screen instead of blanking
the page */}
<ViewErrorBoundary>
<Suspense fallback={<Loading />}>
<AppRoutes />
</Suspense>
</ViewErrorBoundary>
</AppContainer>
</HistoryProvider>
</BrowserRouter>
)}
</>
);
};
const StickyBars = styled.div`
position: sticky;
top: ${HEADER_HEIGHT}px;
z-index: 10;
/* Owns the horizontal inset for every bar inside, so their dismiss icons
line up instead of each bar picking its own margin */
margin: 0 2px;
`;
const TestValueContainer = styled.div`
position: fixed;
top: 0;
left: 0;
z-index: 1000;
/* Don't block clicks on the header underneath; children opt back in */
pointer-events: none;
@media (max-width: ${(props) => props.theme.breakpointDesktop}) {
display: none;
}
`;
const AppContainer = styled.div`
@media (max-width: ${(props) => props.theme.breakpointDesktop}) {
margin-left: ${MOBILE_MARGIN}px;
margin-right: ${MOBILE_MARGIN}px;
}
`;
export default App;
|