2021-12-15 09:28:17 +00:00
|
|
|
import { styled } from '@stitches/react';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
|
|
import { useTranslation } from 'react-i18next';
|
|
|
|
import { useSelector } from 'react-redux';
|
|
|
|
import { Link, useMatch, useResolvedPath } from 'react-router-dom';
|
2021-12-16 16:01:24 +00:00
|
|
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
2021-12-15 09:28:17 +00:00
|
|
|
import { RootState } from '../../store';
|
2021-12-16 16:01:24 +00:00
|
|
|
import { APPNAME, APPREPO } from '../theme';
|
2021-12-15 09:28:17 +00:00
|
|
|
|
2022-01-02 10:46:07 +00:00
|
|
|
// @ts-expect-error Asset import
|
|
|
|
import logo from '../../assets/icon-logo.svg';
|
|
|
|
|
2021-12-15 09:28:17 +00:00
|
|
|
export interface RouteSection {
|
|
|
|
title: string;
|
|
|
|
links: Route[];
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface Route {
|
|
|
|
title: string;
|
|
|
|
url: string;
|
|
|
|
icon?: JSX.Element;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface SidebarProps {
|
|
|
|
sections: RouteSection[];
|
|
|
|
}
|
|
|
|
|
|
|
|
const Container = styled('section', {
|
2021-12-16 16:01:24 +00:00
|
|
|
background: '$gray1',
|
2021-12-15 09:28:17 +00:00
|
|
|
maxWidth: '220px',
|
|
|
|
borderRight: '1px solid $gray6',
|
|
|
|
});
|
|
|
|
|
|
|
|
const Header = styled('div', {
|
|
|
|
padding: '0.8rem 1rem 1rem 0.8rem',
|
|
|
|
});
|
|
|
|
|
|
|
|
const AppName = styled('h1', {
|
2022-01-02 10:46:07 +00:00
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
gap: '0.2rem',
|
2021-12-15 09:28:17 +00:00
|
|
|
fontSize: '1.4rem',
|
2022-01-02 10:46:07 +00:00
|
|
|
margin: '0.5rem 0 0.5rem 0',
|
|
|
|
fontWeight: 300,
|
2021-12-15 09:28:17 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const VersionLabel = styled('div', {
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
fontSize: '0.75rem',
|
|
|
|
fontWeight: 'bold',
|
|
|
|
color: '$teal8',
|
2022-01-02 10:46:07 +00:00
|
|
|
paddingLeft: '12px',
|
2021-12-15 09:28:17 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const UpdateButton = styled('a', {
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
fontSize: '0.75rem',
|
|
|
|
fontWeight: 'bold',
|
|
|
|
color: '$yellow12',
|
|
|
|
border: '1px solid $yellow7',
|
|
|
|
padding: '0.2rem 0.4rem',
|
|
|
|
marginTop: '0.5rem',
|
|
|
|
backgroundColor: '$yellow5',
|
|
|
|
borderRadius: '0.2rem',
|
|
|
|
display: 'inline-block',
|
|
|
|
cursor: 'pointer',
|
|
|
|
textDecoration: 'none',
|
|
|
|
'&:hover': {
|
|
|
|
backgroundColor: '$yellow6',
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
const MenuSection = styled('article', {
|
|
|
|
display: 'flex',
|
|
|
|
flexDirection: 'column',
|
|
|
|
padding: '0.2rem 0 0.5rem 0',
|
|
|
|
});
|
|
|
|
const MenuHeader = styled('header', {
|
|
|
|
textTransform: 'uppercase',
|
|
|
|
fontSize: '0.75rem',
|
|
|
|
fontWeight: 'bold',
|
|
|
|
padding: '0.5rem 0 0.5rem 0.8rem',
|
|
|
|
color: '$teal9',
|
|
|
|
});
|
|
|
|
const MenuLink = styled(Link, {
|
2021-12-16 16:01:24 +00:00
|
|
|
color: '$teal13 !important',
|
2021-12-15 09:28:17 +00:00
|
|
|
display: 'flex',
|
|
|
|
alignItems: 'center',
|
|
|
|
textDecoration: 'none',
|
|
|
|
gap: '0.6rem',
|
|
|
|
padding: '0.6rem 1.6rem 0.6rem 1rem',
|
|
|
|
fontSize: '0.9rem',
|
|
|
|
fontWeight: '300',
|
|
|
|
variants: {
|
|
|
|
status: {
|
|
|
|
selected: {
|
2021-12-16 16:01:24 +00:00
|
|
|
color: '$teal13 !important',
|
2021-12-15 09:28:17 +00:00
|
|
|
backgroundColor: '$teal5',
|
|
|
|
},
|
|
|
|
clickable: {
|
|
|
|
cursor: 'pointer',
|
|
|
|
'&:hover': {
|
|
|
|
backgroundColor: '$teal4',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
function SidebarLink({ route: { title, url, icon } }: { route: Route }) {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const resolved = useResolvedPath(url);
|
|
|
|
const match = useMatch({ path: resolved.pathname, end: true });
|
|
|
|
return (
|
|
|
|
<MenuLink
|
|
|
|
status={match ? 'selected' : 'clickable'}
|
|
|
|
to={url.replace(/\/\//gi, '/')}
|
|
|
|
key={`${title}-${url}`}
|
|
|
|
>
|
|
|
|
{icon}
|
|
|
|
{t(title)}
|
|
|
|
</MenuLink>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function Sidebar({
|
|
|
|
sections,
|
|
|
|
}: SidebarProps): React.ReactElement {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const client = useSelector((state: RootState) => state.api.client);
|
|
|
|
const [version, setVersion] = useState<string>(null);
|
|
|
|
const [lastVersion, setLastVersion] =
|
|
|
|
useState<{ url: string; name: string }>(null);
|
|
|
|
const dev = version && version.startsWith('v0.0.0');
|
|
|
|
|
|
|
|
async function fetchVersion() {
|
|
|
|
const versionString = await client.getKey('stul-meta/version');
|
|
|
|
setVersion(versionString);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function fetchLastVersion() {
|
|
|
|
try {
|
|
|
|
const req = await fetch(
|
|
|
|
`https://api.github.com/repos/${APPREPO}/releases/latest`,
|
|
|
|
{
|
|
|
|
headers: {
|
|
|
|
Accept: 'application/vnd.github.v3+json',
|
|
|
|
},
|
|
|
|
},
|
|
|
|
);
|
|
|
|
const data = await req.json();
|
|
|
|
setLastVersion({
|
|
|
|
url: data.html_url,
|
|
|
|
name: data.name,
|
|
|
|
});
|
|
|
|
} catch (e) {
|
|
|
|
// TODO Report error nicely
|
|
|
|
console.warn('Failed checking upstream for latest version', e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
fetchLastVersion();
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (client) {
|
|
|
|
fetchVersion();
|
|
|
|
}
|
|
|
|
}, [client]);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Container>
|
2021-12-16 16:01:24 +00:00
|
|
|
<OverlayScrollbarsComponent
|
|
|
|
style={{ maxHeight: '100vh' }}
|
|
|
|
options={{ scrollbars: { autoHide: 'scroll' } }}
|
|
|
|
>
|
|
|
|
<Header>
|
2022-01-02 10:46:07 +00:00
|
|
|
<AppName>
|
|
|
|
<img src={logo} style={{ height: '28px', marginBottom: '-2px' }} />
|
|
|
|
{APPNAME}
|
|
|
|
</AppName>
|
2021-12-16 16:01:24 +00:00
|
|
|
<VersionLabel>
|
|
|
|
{version && !dev ? version : t('debug.dev-build')}
|
|
|
|
</VersionLabel>
|
|
|
|
{!dev && lastVersion && !version.startsWith(lastVersion.name) && (
|
|
|
|
<UpdateButton href={lastVersion.url}>
|
|
|
|
{t('menu.messages.update-available')}
|
|
|
|
</UpdateButton>
|
|
|
|
)}
|
|
|
|
</Header>
|
|
|
|
{sections.map(({ title: sectionTitle, links }) => (
|
|
|
|
<MenuSection key={sectionTitle}>
|
|
|
|
<MenuHeader>{t(sectionTitle)}</MenuHeader>
|
|
|
|
{links.map((route) => (
|
|
|
|
<SidebarLink route={route} key={`${route.title}-${route.url}`} />
|
|
|
|
))}
|
|
|
|
</MenuSection>
|
|
|
|
))}
|
|
|
|
</OverlayScrollbarsComponent>
|
2021-12-15 09:28:17 +00:00
|
|
|
</Container>
|
|
|
|
);
|
|
|
|
}
|