Files
pezkuwi-apps/packages/apps/src/Content/index.tsx
T

133 lines
3.5 KiB
TypeScript

// Copyright 2017-2026 @pezkuwi/apps authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Route } from '@pezkuwi/apps-routing/types';
import React, { Suspense, useMemo } from 'react';
import { useLocation } from 'react-router-dom';
import createRoutes from '@pezkuwi/apps-routing';
import { ErrorBoundary, Spinner, styled } from '@pezkuwi/react-components';
import { useApi, useQueue } from '@pezkuwi/react-hooks';
import { TabsCtx } from '@pezkuwi/react-hooks/ctx/Tabs';
import { findMissingApis } from '../endpoint.js';
import { useTranslation } from '../translate.js';
import BrowserCheckAlert from './BrowserCheckAlert.js';
import NotFound from './NotFound.js';
import Status from './Status.js';
interface Props {
className?: string;
}
const NOT_FOUND: Route = {
Component: NotFound,
display: {},
group: 'settings',
icon: 'times',
isIgnored: false,
name: 'unknown',
text: 'Unknown'
};
function Content ({ className }: Props): React.ReactElement<Props> {
const location = useLocation();
const { t } = useTranslation();
const { api, isApiConnected, isApiReady, isDevelopment } = useApi();
const { queueAction } = useQueue();
const { Component, display: { needsApi, needsApiCheck, needsApiInstances }, icon, name, text } = useMemo(
(): Route => {
const app = location.pathname.slice(1) || '';
return createRoutes(t).find((r) =>
r &&
app.startsWith(r.name) &&
(isDevelopment || !r.display.isDevelopment)
) || NOT_FOUND;
},
[isDevelopment, location, t]
);
const missingApis = useMemo(
() => needsApi
? isApiReady && isApiConnected
? findMissingApis(api, needsApi, needsApiInstances, needsApiCheck)
: null
: [],
[api, isApiConnected, isApiReady, needsApi, needsApiCheck, needsApiInstances]
);
return (
<StyledDiv className={className}>
{!missingApis
? (
<div className='connecting'>
<BrowserCheckAlert />
<Spinner label={t('Initializing connection')} />
</div>
)
: (
<>
<Suspense fallback='...'>
<ErrorBoundary trigger={name}>
<TabsCtx.Provider value={{ icon, text }}>
{missingApis.length
? (
<NotFound
basePath={`/${name}`}
location={location}
missingApis={missingApis}
onStatusChange={queueAction}
/>
)
: (
<Component
basePath={`/${name}`}
location={location}
onStatusChange={queueAction}
/>
)
}
</TabsCtx.Provider>
</ErrorBoundary>
</Suspense>
<Status />
</>
)
}
</StyledDiv>
);
}
const StyledDiv = styled.div`
flex-grow: 1;
overflow: hidden auto;
padding: 0 0 1rem 0;
position: relative;
width: 100%;
.connecting {
padding: 3.5rem 0;
}
& main > *:not(header):not(.hasOwnMaxWidth) {
max-width: var(--width-full);
margin-right: auto;
margin-left: auto;
width: 100%;
padding: 0 1.5rem;
@media only screen and (max-width: 1100px) {
padding: 0 1rem;
}
@media only screen and (max-width: 800px) {
padding: 0 0.75rem;
}
}
`;
export default React.memo(Content);