change how we render layouts

This commit is contained in:
Dawid Wysokiński 2021-01-02 00:43:20 +12:00
parent ea71ef5e0b
commit 3e87c42e0b
29 changed files with 592 additions and 634 deletions

View File

@ -4,25 +4,44 @@ import { heights } from '@theme/toolbar';
import { makeStyles } from '@material-ui/core/styles';
export interface Props extends React.HTMLProps<HTMLElement> {
export interface Props extends React.HTMLAttributes<HTMLOrSVGElement> {
footer?: boolean;
component?: keyof JSX.IntrinsicElements;
minHeight?: boolean;
}
interface MakeStylesProps {
footer?: boolean;
}
function Content({ className, footer = false, ...props }: Props) {
function Content({
className,
footer = false,
component: Wrapper = 'main',
minHeight = true,
...props
}: Props) {
const classes = useStyles({ footer });
return <main className={clsx(classes.main, className)} {...props} />;
return (
<Wrapper
className={clsx(
{ [classes.minHeight]: minHeight },
classes.padding,
className
)}
{...props}
/>
);
}
const useStyles = makeStyles(theme => {
return {
main: ({ footer }: MakeStylesProps) => {
padding: {
padding: theme.spacing(3, 0),
},
minHeight: ({ footer }: MakeStylesProps) => {
const multiplier = footer ? 2 : 1;
return {
padding: theme.spacing(3, 0),
minHeight: `calc(100vh - ${heights.tabletDesktop * multiplier}px)`,
[`${theme.breakpoints.down('xs')} and (orientation: landscape)`]: {
minHeight: `calc(100vh - ${heights.mobileLandscape * multiplier}px)`,

View File

@ -1,5 +1,7 @@
import React from 'react';
import { StringParam, useQueryParam, withDefault } from 'use-query-params';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { TWHELP, NAME } from '@config/app';
import * as ROUTES from '@config/routes';
import * as NAMESPACES from '@config/namespaces';
@ -21,20 +23,14 @@ import VersionSelector from '@common/VersionSelector/VersionSelector';
import SearchInput from './SearchInput';
export interface Props {
showLinkToHomePage?: boolean;
hideVersionSelectorOnMobile?: boolean;
defaultQ?: string;
appBarProps?: AppBarProps;
}
export default function Header({
showLinkToHomePage = true,
hideVersionSelectorOnMobile = false,
defaultQ = '',
appBarProps = {},
}: Props) {
export default function Header({ appBarProps = {} }: Props) {
const { t } = useTranslation(NAMESPACES.COMMON);
const location = useLocation();
const classes = useStyles();
const [q] = useQueryParam('q', withDefault(StringParam, ''));
const versionSelector = (
<div>
@ -46,9 +42,11 @@ export default function Header({
<Container>
<Toolbar disableGutters className={classes.toolbar}>
<form className={classes.form}>
<SearchInput defaultQ={defaultQ} />
<SearchInput
defaultQ={location.pathname === ROUTES.SEARCH_PAGE ? q : ''}
/>
</form>
{showLinkToHomePage && (
{location.pathname !== ROUTES.INDEX_PAGE && (
<Link to={ROUTES.INDEX_PAGE}>
<Hidden xsDown implementation="css">
<Button startIcon={<InputIcon />}>
@ -62,7 +60,7 @@ export default function Header({
</Hidden>
</Link>
)}
{hideVersionSelectorOnMobile ? (
{location.pathname !== ROUTES.INDEX_PAGE ? (
<Hidden xsDown implementation="css">
{versionSelector}
</Hidden>

View File

@ -7,6 +7,7 @@ import ScrollToTop from '@common/ScrollToTop/ScrollToTop';
import VersionProvider from '../libs/VersionContext/Provider';
import DateUtilsProvider from '../libs/date/DateUtilsProvider';
import MainLayout from '../common/MainLayout/MainLayout';
import IndexPage from './IndexPage/IndexPage';
import NotFoundPage from './NotFoundPage/NotFoundPage';
import SearchPage from './SearchPage/SearchPage';
@ -18,11 +19,17 @@ function App() {
<VersionProvider>
<DateUtilsProvider>
<Switch>
<Route path={ROUTES.INDEX_PAGE} exact>
<IndexPage />
</Route>
<Route path={ROUTES.SEARCH_PAGE} exact>
<SearchPage />
<Route path={[ROUTES.INDEX_PAGE, ROUTES.SEARCH_PAGE]} exact>
<MainLayout>
<Switch>
<Route path={ROUTES.INDEX_PAGE} exact>
<IndexPage />
</Route>
<Route path={ROUTES.SEARCH_PAGE} exact>
<SearchPage />
</Route>
</Switch>
</MainLayout>
</Route>
<Route path={ROUTES.SERVER_PAGE.INDEX_PAGE}>
<ServerPage />

View File

@ -4,7 +4,6 @@ import useTitle from '@libs/useTitle';
import { INDEX_PAGE } from '@config/namespaces';
import { Container, Box } from '@material-ui/core';
import MainLayout from '@common/MainLayout/MainLayout';
import DevNote from '@common/DevNote/DevNote';
import ServerSelection from './components/ServerSelection/ServerSelection';
@ -12,18 +11,11 @@ export default function IndexPage() {
const { t } = useTranslation(INDEX_PAGE);
useTitle(t('title'));
return (
<MainLayout
headerProps={{
showLinkToHomePage: false,
hideVersionSelectorOnMobile: false,
}}
>
<Container>
<Box mb={3}>
<DevNote />
</Box>
<ServerSelection />
</Container>
</MainLayout>
<Container>
<Box mb={3}>
<DevNote />
</Box>
<ServerSelection />
</Container>
);
}

View File

@ -1,4 +1,4 @@
import React, { useRef } from 'react';
import React, { useRef, useState } from 'react';
import formatNumber from '@utils/formatNumber';
import * as ROUTES from '@config/routes';
import * as NAMESPACES from '@config/namespaces';
@ -31,7 +31,7 @@ export interface Props {
function GridItem({ t, server, hideTooltip = true }: Props) {
const classes = useStyles();
const [expanded, setExpanded] = React.useState<boolean>(false);
const [expanded, setExpanded] = useState<boolean>(false);
const accordion = useRef<HTMLDivElement | null>(null);
const handleClick = (e: React.ChangeEvent<{}>, expanded: boolean) => {

View File

@ -14,7 +14,6 @@ import { SEARCH_PAGE } from '@config/namespaces';
import { MODES, LIMIT } from './constants';
import { Container, Paper } from '@material-ui/core';
import MainLayout from '@common/MainLayout/MainLayout';
import ModeSelector from '@common/ModeSelector/ModeSelector';
import PlayerTable from './components/PlayerTable/PlayerTable';
import TribeTable from './components/TribeTable/TribeTable';
@ -55,39 +54,35 @@ function SearchPage() {
};
return (
<MainLayout
headerProps={{ hideVersionSelectorOnMobile: true, defaultQ: query.q }}
>
<Container>
<Paper>
<ModeSelector
buttonProps={{ size: 'medium' }}
onSelect={m => setQuery({ mode: m.name, page: 0, limit: LIMIT })}
modes={[
{
name: 'player',
label: t('modes.player'),
get selected() {
return this.name === query.mode;
},
<Container>
<Paper>
<ModeSelector
buttonProps={{ size: 'medium' }}
onSelect={m => setQuery({ mode: m.name, page: 0, limit: LIMIT })}
modes={[
{
name: 'player',
label: t('modes.player'),
get selected() {
return this.name === query.mode;
},
{
name: 'tribe',
label: t('modes.tribe'),
get selected() {
return this.name === query.mode;
},
},
{
name: 'tribe',
label: t('modes.tribe'),
get selected() {
return this.name === query.mode;
},
]}
/>
{query.mode === MODES.TRIBE ? (
<TribeTable {...tableProps} />
) : (
<PlayerTable {...tableProps} />
)}
</Paper>
</Container>
</MainLayout>
},
]}
/>
{query.mode === MODES.TRIBE ? (
<TribeTable {...tableProps} />
) : (
<PlayerTable {...tableProps} />
)}
</Paper>
</Container>
);
}

View File

@ -3,6 +3,7 @@ import { SERVER_PAGE } from '@config/routes';
import { Switch, Route } from 'react-router-dom';
import ServerProvider from './libs/ServerContext/Provider';
import PageLayout from './common/PageLayout/PageLayout';
import IndexPage from './features/IndexPage/IndexPage';
import PlayerPage from './features/PlayerPage/PlayerPage';
import TribePage from './features/TribePage/TribePage';
@ -16,35 +17,37 @@ import NotFoundPage from './features/NotFoundPage/NotFoundPage';
function ServerPage() {
return (
<ServerProvider>
<Switch>
<Route exact path={SERVER_PAGE.INDEX_PAGE}>
<IndexPage />
</Route>
<Route path={SERVER_PAGE.PLAYER_PAGE.INDEX_PAGE}>
<PlayerPage />
</Route>
<Route path={SERVER_PAGE.TRIBE_PAGE.INDEX_PAGE}>
<TribePage />
</Route>
<Route exact path={SERVER_PAGE.VILLAGE_PAGE.INDEX_PAGE}>
<VillagePage />
</Route>
<Route path={SERVER_PAGE.RANKING_PAGE.BASE}>
<RankingPage />
</Route>
<Route path={SERVER_PAGE.MAP_PAGE}>
<MapPage />
</Route>
<Route exact path={SERVER_PAGE.ENNOBLEMENTS_PAGE}>
<EnnoblementsPage />
</Route>
<Route exact path={SERVER_PAGE.WAR_STATS_PAGE}>
<WarStatsPage />
</Route>
<Route path="*">
<NotFoundPage />
</Route>
</Switch>
<PageLayout>
<Switch>
<Route exact path={SERVER_PAGE.INDEX_PAGE}>
<IndexPage />
</Route>
<Route path={SERVER_PAGE.PLAYER_PAGE.INDEX_PAGE}>
<PlayerPage />
</Route>
<Route path={SERVER_PAGE.TRIBE_PAGE.INDEX_PAGE}>
<TribePage />
</Route>
<Route exact path={SERVER_PAGE.VILLAGE_PAGE.INDEX_PAGE}>
<VillagePage />
</Route>
<Route path={SERVER_PAGE.RANKING_PAGE.BASE}>
<RankingPage />
</Route>
<Route path={SERVER_PAGE.MAP_PAGE}>
<MapPage />
</Route>
<Route exact path={SERVER_PAGE.ENNOBLEMENTS_PAGE}>
<EnnoblementsPage />
</Route>
<Route exact path={SERVER_PAGE.WAR_STATS_PAGE}>
<WarStatsPage />
</Route>
<Route path="*">
<NotFoundPage />
</Route>
</Switch>
</PageLayout>
</ServerProvider>
);
}

View File

@ -7,23 +7,15 @@ import { DRAWER_WIDTH } from './components/Sidebar/contants';
import { makeStyles, useTheme } from '@material-ui/core/styles';
import { useMediaQuery, Toolbar } from '@material-ui/core';
import Content, { Props as ContentProps } from '@common/Content/Content';
import Content from '@common/Content/Content';
import Sidebar from './components/Sidebar/Sidebar';
import TopBar from './components/TopBar/TopBar';
export interface Props {
children?: React.ReactNode;
noPadding?: boolean;
contentClassName?: string;
contentStyle?: ContentProps['style'];
}
function PageLayout({
children,
noPadding,
contentClassName,
contentStyle,
}: Props) {
function PageLayout({ children }: Props) {
const [open, setOpen] = useState(false);
const classes = useStyles();
const theme = useTheme();
@ -56,14 +48,7 @@ function PageLayout({
t={t}
onOpen={openSidebar}
/>
<Content
className={clsx(classes.content, contentClassName, {
'no-padding': noPadding,
})}
style={contentStyle}
>
{children}
</Content>
<Content className={classes.content}>{children}</Content>
</div>
);
}
@ -81,10 +66,7 @@ const useStyles = makeStyles(theme => ({
},
content: {
height: '100%',
padding: theme.spacing(3, 0),
'&.no-padding': {
padding: '0 0',
},
padding: 0,
},
}));

View File

@ -53,6 +53,7 @@ const Sidebar = ({ t, className, open, variant, onClose, onOpen }: Props) => {
name: t('pageLayout.sidebar.routes.rankings.name'),
Icon: <GradeIcon color="inherit" />,
to: ROUTES.SERVER_PAGE.RANKING_PAGE.BASE,
isExpandable: true,
nested: [
{
name: t('pageLayout.sidebar.routes.rankings.player.index'),

View File

@ -1,4 +1,4 @@
import React, { Fragment, useState } from 'react';
import React, { Fragment, useState, memo } from 'react';
import { useLocation, matchPath } from 'react-router-dom';
import { Route } from './types';
@ -26,7 +26,7 @@ function ListItem({ route, nestedLevel }: Props) {
const isActive =
!!route.to && !!matchPath(pathname, { path: route.to, exact: route.exact });
const [open, setOpen] = useState(
(hasNested && isActive) || !route.isExpandable
(hasNested && !route.isExpandable) || isActive
);
const getItem = () => {
@ -97,4 +97,4 @@ const useStyles = makeStyles(theme => ({
},
}));
export default ListItem;
export default memo(ListItem);

View File

@ -1,7 +1,6 @@
import React from 'react';
import { Route } from './types';
import { makeStyles } from '@material-ui/core/styles';
import { List } from '@material-ui/core';
import ListItem from './ListItem';
@ -9,15 +8,9 @@ export interface Props {
routes: Route[];
}
const useStyles = makeStyles(theme => ({
root: {},
}));
const Nav = ({ routes }: Props) => {
const classes = useStyles();
return (
<List className={classes.root}>
<List>
{routes.map(route => (
<ListItem nestedLevel={1} route={route} key={route.name} />
))}

View File

@ -6,7 +6,7 @@ import useServer from '@features/ServerPage/libs/ServerContext/useServer';
import { SERVER_PAGE } from '@config/namespaces';
import { Container, Paper } from '@material-ui/core';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Content from '@common/Content/Content';
import ModeSelector from '@common/ModeSelector/ModeSelector';
import LiveEnnoblements from './components/LiveEnnoblements/LiveEnnoblements';
import LatestSavedEnnoblements from './components/LatestSavedEnnoblements/LatestSavedEnnoblements';
@ -22,7 +22,7 @@ function EnnoblementsPage() {
);
return (
<ServerPageLayout>
<Content component="div" minHeight={false}>
<Container>
<Paper>
<ModeSelector
@ -53,7 +53,7 @@ function EnnoblementsPage() {
)}
</Paper>
</Container>
</ServerPageLayout>
</Content>
);
}

View File

@ -5,7 +5,7 @@ import useServer from '../../libs/ServerContext/useServer';
import { SERVER_PAGE } from '@config/namespaces';
import { Container, Grid, Hidden } from '@material-ui/core';
import PageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Content from '@common/Content/Content';
import PlayerStatistics from './components/PlayerStatistics/PlayerStatistics';
import TribeStatistics from './components/TribeStatistics/TribeStatistics';
import Top5Players from './components/Top5Players/Top5Players';
@ -23,7 +23,7 @@ function IndexPage() {
useTitle(t('title', { key }));
return (
<PageLayout>
<Content component="div" minHeight={false}>
<Container>
<Grid container spacing={2}>
<Grid
@ -72,7 +72,7 @@ function IndexPage() {
</Grid>
</Grid>
</Container>
</PageLayout>
</Content>
);
}

View File

@ -25,7 +25,7 @@ import {
Checkbox,
FormControlLabel,
} from '@material-ui/core';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Content from '@common/Content/Content';
import ColorInput from '@common/Form/ColorInput';
import Spinner from '@common/Spinner/Spinner';
import Map from './components/Map/Map';
@ -188,275 +188,272 @@ function MapPage() {
return blacklist.some(id2 => id === id2);
};
if (loading) {
return (
<Spinner
containerProps={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
minHeight: 'inherit',
paddingY: 5,
}}
description={t('loading')}
/>
);
}
return (
<ServerPageLayout>
{loading && (
<Spinner
containerProps={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
minHeight: 'inherit',
paddingY: 5,
}}
description={t('loading')}
/>
)}
{!loading && (
<Container>
<form onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Card>
<Typography
variant="h4"
component="h3"
align="center"
gutterBottom
>
{t('sections.settings')}
</Typography>
<div className={classes.formGroup}>
<TextField
label={t('inputLabels.zoomLevel')}
type="number"
name="scale"
value={query.scale}
onChange={createSettingsChangeHandler('scale')}
<Content component="div" minHeight={false}>
<Container>
<form onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Card>
<Typography
variant="h4"
component="h3"
align="center"
gutterBottom
>
{t('sections.settings')}
</Typography>
<div className={classes.formGroup}>
<TextField
label={t('inputLabels.zoomLevel')}
type="number"
name="scale"
value={query.scale}
onChange={createSettingsChangeHandler('scale')}
fullWidth
variant="standard"
inputProps={{
min: 1,
max: 5,
step: '.1',
}}
/>
<TextField
label={t('inputLabels.centerX')}
type="number"
name="centerX"
value={query.centerX}
onChange={createSettingsChangeHandler('centerX')}
fullWidth
variant="standard"
inputProps={{
min: 0,
max: 1000,
step: '1',
}}
/>
<TextField
label={t('inputLabels.centerY')}
type="number"
name="centerY"
value={query.centerY}
onChange={createSettingsChangeHandler('centerY')}
fullWidth
variant="standard"
inputProps={{
min: 0,
max: 1000,
step: '1',
}}
/>
{[
{
name: 'backgroundColor',
color: query.backgroundColor,
onChange: createSettingsChangeHandler('backgroundColor'),
},
{
name: 'playerVillageColor',
color: query.playerVillageColor,
onChange: createSettingsChangeHandler('playerVillageColor'),
},
{
name: 'barbarianVillageColor',
color: query.barbarianVillageColor,
onChange: createSettingsChangeHandler(
'barbarianVillageColor'
),
},
{
name: 'gridLineColor',
color: query.gridLineColor,
onChange: createSettingsChangeHandler('gridLineColor'),
},
{
name: 'continentNumberColor',
color: query.continentNumberColor,
onChange: createSettingsChangeHandler(
'continentNumberColor'
),
},
].map(({ color, name, onChange }) => (
<ColorInput
key={name}
color={color}
onChange={onChange}
fullWidth
variant="standard"
inputProps={{
min: 1,
max: 5,
step: '.1',
}}
name={name}
label={t('inputLabels.' + name)}
/>
<TextField
label={t('inputLabels.centerX')}
type="number"
name="centerX"
value={query.centerX}
onChange={createSettingsChangeHandler('centerX')}
fullWidth
variant="standard"
inputProps={{
min: 0,
max: 1000,
step: '1',
}}
/>
<TextField
label={t('inputLabels.centerY')}
type="number"
name="centerY"
value={query.centerY}
onChange={createSettingsChangeHandler('centerY')}
fullWidth
variant="standard"
inputProps={{
min: 0,
max: 1000,
step: '1',
}}
/>
{[
{
name: 'backgroundColor',
color: query.backgroundColor,
onChange: createSettingsChangeHandler('backgroundColor'),
},
{
name: 'playerVillageColor',
color: query.playerVillageColor,
onChange: createSettingsChangeHandler(
'playerVillageColor'
),
},
{
name: 'barbarianVillageColor',
color: query.barbarianVillageColor,
onChange: createSettingsChangeHandler(
'barbarianVillageColor'
),
},
{
name: 'gridLineColor',
color: query.gridLineColor,
onChange: createSettingsChangeHandler('gridLineColor'),
},
{
name: 'continentNumberColor',
color: query.continentNumberColor,
onChange: createSettingsChangeHandler(
'continentNumberColor'
),
},
].map(({ color, name, onChange }) => (
<ColorInput
))}
{[
{
name: 'markersOnly',
checked: query.markersOnly,
onChange: createSettingsChangeHandler('markersOnly'),
},
{
name: 'showBarbarian',
checked: query.showBarbarian,
onChange: createSettingsChangeHandler('showBarbarian'),
},
{
name: 'largerMarkers',
checked: query.largerMarkers,
onChange: createSettingsChangeHandler('largerMarkers'),
},
{
name: 'showGrid',
checked: query.showGrid,
onChange: createSettingsChangeHandler('showGrid'),
},
{
name: 'showContinentNumbers',
checked: query.showContinentNumbers,
onChange: createSettingsChangeHandler(
'showContinentNumbers'
),
},
].map(({ name, checked, onChange }) => {
return (
<FormControlLabel
key={name}
color={color}
onChange={onChange}
fullWidth
variant="standard"
name={name}
label={t('inputLabels.' + name)}
control={
<Checkbox
name={name}
checked={checked}
onChange={onChange}
/>
}
/>
))}
{[
{
name: 'markersOnly',
checked: query.markersOnly,
onChange: createSettingsChangeHandler('markersOnly'),
},
{
name: 'showBarbarian',
checked: query.showBarbarian,
onChange: createSettingsChangeHandler('showBarbarian'),
},
{
name: 'largerMarkers',
checked: query.largerMarkers,
onChange: createSettingsChangeHandler('largerMarkers'),
},
{
name: 'showGrid',
checked: query.showGrid,
onChange: createSettingsChangeHandler('showGrid'),
},
{
name: 'showContinentNumbers',
checked: query.showContinentNumbers,
onChange: createSettingsChangeHandler(
'showContinentNumbers'
),
},
].map(({ name, checked, onChange }) => {
return (
<FormControlLabel
key={name}
label={t('inputLabels.' + name)}
control={
<Checkbox
name={name}
checked={checked}
onChange={onChange}
/>
}
/>
);
})}
</div>
</Card>
<Card>
<Typography
variant="h4"
component="h3"
align="center"
gutterBottom
);
})}
</div>
</Card>
<Card>
<Typography
variant="h4"
component="h3"
align="center"
gutterBottom
>
{t('sections.tribeMarkers')}
</Typography>
<div className={classes.formGroup}>
{tribeMarkers.map(marker => {
return (
<MarkerField
key={marker.id}
onDelete={createDeleteTribeMarkerHandler(marker.id)}
onChange={createUpdateTribeMarkerItemHandler(marker.id)}
onChangeColor={createUpdateTribeMarkerColorHandler(
marker.id
)}
loadingText={t('loading')}
noOptionsText={t('noOptions')}
loadSuggestions={searchTribes}
getOptionLabel={tribeGetOptionLabel}
getOptionSelected={getOptionSelected}
getOptionDisabled={opt =>
isDisabled(opt.id, selectedTribeIDs)
}
color={marker.color}
value={marker.item}
/>
);
})}
<Button
variant="contained"
fullWidth
color="secondary"
onClick={handleAddTribeMarker}
disabled={tribeMarkers.length >= 25}
>
{t('sections.tribeMarkers')}
</Typography>
<div className={classes.formGroup}>
{tribeMarkers.map(marker => {
return (
<MarkerField
key={marker.id}
onDelete={createDeleteTribeMarkerHandler(marker.id)}
onChange={createUpdateTribeMarkerItemHandler(marker.id)}
onChangeColor={createUpdateTribeMarkerColorHandler(
marker.id
)}
loadingText={t('loading')}
noOptionsText={t('noOptions')}
loadSuggestions={searchTribes}
getOptionLabel={tribeGetOptionLabel}
getOptionSelected={getOptionSelected}
getOptionDisabled={opt =>
isDisabled(opt.id, selectedTribeIDs)
}
color={marker.color}
value={marker.item}
/>
);
})}
<Button
variant="contained"
fullWidth
color="secondary"
onClick={handleAddTribeMarker}
disabled={tribeMarkers.length >= 25}
>
{t('buttons.addMarker')}
</Button>
</div>
</Card>
<Card>
<Typography
variant="h4"
component="h3"
align="center"
gutterBottom
{t('buttons.addMarker')}
</Button>
</div>
</Card>
<Card>
<Typography
variant="h4"
component="h3"
align="center"
gutterBottom
>
{t('sections.playerMarkers')}
</Typography>
<div className={classes.formGroup}>
{playerMarkers.map(marker => {
return (
<MarkerField
key={marker.id}
onDelete={createDeletePlayerMarkerHandler(marker.id)}
onChange={createUpdatePlayerMarkerItemHandler(marker.id)}
onChangeColor={createUpdatePlayerMarkerColorHandler(
marker.id
)}
noOptionsText={t('noOptions')}
loadSuggestions={searchPlayers}
getOptionLabel={playerGetOptionLabel}
getOptionSelected={getOptionSelected}
getOptionDisabled={opt =>
isDisabled(opt.id, selectedPlayerIDs)
}
color={marker.color}
value={marker.item}
/>
);
})}
<Button
variant="contained"
fullWidth
color="secondary"
onClick={handleAddPlayerMarker}
disabled={playerMarkers.length >= 25}
>
{t('sections.playerMarkers')}
</Typography>
<div className={classes.formGroup}>
{playerMarkers.map(marker => {
return (
<MarkerField
key={marker.id}
onDelete={createDeletePlayerMarkerHandler(marker.id)}
onChange={createUpdatePlayerMarkerItemHandler(
marker.id
)}
onChangeColor={createUpdatePlayerMarkerColorHandler(
marker.id
)}
noOptionsText={t('noOptions')}
loadSuggestions={searchPlayers}
getOptionLabel={playerGetOptionLabel}
getOptionSelected={getOptionSelected}
getOptionDisabled={opt =>
isDisabled(opt.id, selectedPlayerIDs)
}
color={marker.color}
value={marker.item}
/>
);
})}
<Button
variant="contained"
fullWidth
color="secondary"
onClick={handleAddPlayerMarker}
disabled={playerMarkers.length >= 25}
>
{t('buttons.addMarker')}
</Button>
</div>
</Card>
<Grid item xs={12}>
<Typography align="center" component="div">
<Button
type="submit"
size="large"
color="secondary"
variant="contained"
>
{t('buttons.generateMap')}
</Button>
</Typography>
</Grid>
{mapURL && (
<Grid item xs={12}>
<Map src={mapURL} alt={key} t={t} />
</Grid>
)}
{t('buttons.addMarker')}
</Button>
</div>
</Card>
<Grid item xs={12}>
<Typography align="center" component="div">
<Button
type="submit"
size="large"
color="secondary"
variant="contained"
>
{t('buttons.generateMap')}
</Button>
</Typography>
</Grid>
</form>
</Container>
)}
</ServerPageLayout>
{mapURL && (
<Grid item xs={12}>
<Map src={mapURL} alt={key} t={t} />
</Grid>
)}
</Grid>
</form>
</Container>
</Content>
);
}

View File

@ -5,11 +5,10 @@ import { NOT_FOUND_PAGE } from '@config/namespaces';
import { makeStyles } from '@material-ui/core/styles';
import { Typography } from '@material-ui/core';
import PageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Content from '@common/Content/Content';
const useStyles = makeStyles(theme => ({
container: {
padding: theme.spacing(3, 0),
width: '100%',
minHeight: 'inherit',
display: 'flex',
@ -24,40 +23,20 @@ const useStyles = makeStyles(theme => ({
export interface Props {
title?: string;
description?: string;
wrapIntoServerPageLayout?: boolean;
}
function NotFoundPage({
title,
description,
wrapIntoServerPageLayout = true,
}: Props) {
function NotFoundPage({ title, description }: Props) {
const classes = useStyles();
const { t } = useTranslation(NOT_FOUND_PAGE);
useTitle(t('title'));
const jsx = (
<div className={classes.container}>
return (
<Content component="div" className={classes.container}>
<div>
<Typography variant="h1">{title ? title : t('title')}</Typography>
{description && <Typography variant="h4">{description}</Typography>}
</div>
</div>
);
if (!wrapIntoServerPageLayout) {
return jsx;
}
return (
<PageLayout noPadding>
<div className={classes.container}>
<div>
<Typography variant="h1">{title ? title : t('title')}</Typography>
{description && <Typography variant="h4">{description}</Typography>}
</div>
</div>
</PageLayout>
</Content>
);
}

View File

@ -28,7 +28,7 @@ function PlayerPage() {
<EnnoblementsPage />
</Route>
<Route path="*">
<NotFoundPage wrapIntoServerPageLayout={false} />
<NotFoundPage />
</Route>
</Switch>
</PageLayout>

View File

@ -11,9 +11,9 @@ import * as ROUTES from '@config/routes';
import { makeStyles } from '@material-ui/core/styles';
import { Chip, Toolbar, Typography, Tabs, ChipProps } from '@material-ui/core';
import Content from '@common/Content/Content';
import Link from '@common/Link/Link';
import TabLink from '@common/Link/TabLink';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import background1 from './backgrounds/bg-1-dark.png';
import background2 from './backgrounds/bg-2-dark.jpg';
@ -37,7 +37,7 @@ function PageLayout({ children }: Props) {
size: 'small',
};
return (
<ServerPageLayout noPadding>
<div>
<header className={clsx(classes.header, 'bg-' + bg)}>
<Toolbar className={classes.toolbar}>
<div style={{ width: '100%' }}>
@ -124,8 +124,10 @@ function PageLayout({ children }: Props) {
})}
</Tabs>
</header>
<div className={classes.content}>{children}</div>
</ServerPageLayout>
<Content component="div" minHeight={false}>
{children}
</Content>
</div>
);
}
@ -181,13 +183,6 @@ const useStyles = makeStyles(theme => ({
margin: theme.spacing(0.5),
},
},
content: {
height: '100%',
padding: theme.spacing(3, 0),
'&.no-padding': {
padding: '0 0',
},
},
}));
export default PageLayout;

View File

@ -10,7 +10,6 @@ import { PlayerQueryVariables } from '@libs/graphql/types';
import { Params, PlayerQueryResult } from './types';
import NotFoundPage from '@features/ServerPage/features/NotFoundPage/NotFoundPage';
import PageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Spinner from '@common/Spinner/Spinner';
export interface Props {
@ -41,17 +40,15 @@ function Provider({ children }: Props) {
alignItems: 'center',
};
return (
<PageLayout noPadding contentStyle={centerFlex as React.CSSProperties}>
<Spinner
containerProps={{
...centerFlex,
textAlign: 'center',
height: '100%',
paddingY: 5,
}}
description={t('playerPageContextProvider.loadingPlayer')}
/>
</PageLayout>
<Spinner
containerProps={{
...centerFlex,
textAlign: 'center',
minHeight: 'inherit',
paddingY: 5,
}}
description={t('playerPageContextProvider.loadingPlayer')}
/>
);
}

View File

@ -18,7 +18,7 @@ function RankingPage() {
<TribePage />
</Route>
<Route path="*">
<NotFoundPage wrapIntoServerPageLayout={false} />
<NotFoundPage />
</Route>
</Switch>
</PageLayout>

View File

@ -8,7 +8,7 @@ import { makeStyles } from '@material-ui/core/styles';
import { Tabs } from '@material-ui/core';
import TabLink from '@common/Link/TabLink';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Content from '@common/Content/Content';
import background from './backgrounds/bg-1-dark.png';
@ -22,7 +22,7 @@ function PageLayout({ children }: Props) {
const { t } = useTranslation(NAMESPACES.SERVER_PAGE.RANKING_PAGE.COMMON);
const { currentTab, tabs } = useTabs(t);
return (
<ServerPageLayout noPadding>
<div>
<header className={classes.header}>
<Tabs variant="scrollable" value={currentTab}>
{tabs.map(({ to, label }) => {
@ -40,8 +40,10 @@ function PageLayout({ children }: Props) {
})}
</Tabs>
</header>
<div className={classes.content}>{children}</div>
</ServerPageLayout>
<Content minHeight={false} component="div">
{children}
</Content>
</div>
);
}
@ -61,15 +63,6 @@ const useStyles = makeStyles(theme => ({
minHeight: theme.spacing(10),
},
},
content: {
padding: theme.spacing(3, 0),
'&.no-padding': {
padding: '0 0',
},
},
tabs: {
overflowX: 'auto',
},
}));
export default PageLayout;

View File

@ -24,7 +24,7 @@ function PlayerPage() {
<ArchivePage />
</Route>
<Route path="*">
<NotFoundPage wrapIntoServerPageLayout={false} />
<NotFoundPage />
</Route>
</Switch>
);

View File

@ -24,7 +24,7 @@ function TribePage() {
<ArchivePage />
</Route>
<Route path="*">
<NotFoundPage wrapIntoServerPageLayout={false} />
<NotFoundPage />
</Route>
</Switch>
);

View File

@ -32,7 +32,7 @@ function TribePage() {
<EnnoblementsPage />
</Route>
<Route path="*">
<NotFoundPage wrapIntoServerPageLayout={false} />
<NotFoundPage />
</Route>
</Switch>
</PageLayout>

View File

@ -10,8 +10,8 @@ import * as NAMESPACES from '@config/namespaces';
import { makeStyles } from '@material-ui/core/styles';
import { Chip, Toolbar, Typography, Tabs, ChipProps } from '@material-ui/core';
import Content from '@common/Content/Content';
import TabLink from '@common/Link/TabLink';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import background1 from './backgrounds/bg-1-dark.jpg';
import background2 from './backgrounds/bg-2-dark.jpg';
@ -35,7 +35,7 @@ function PageLayout({ children }: Props) {
size: 'small',
};
return (
<ServerPageLayout noPadding>
<div>
<header className={clsx(classes.header, 'bg-' + bg)}>
<Toolbar className={classes.toolbar}>
<div style={{ width: '100%' }}>
@ -105,8 +105,10 @@ function PageLayout({ children }: Props) {
})}
</Tabs>
</header>
<div className={classes.content}>{children}</div>
</ServerPageLayout>
<Content minHeight={false} component="div">
{children}
</Content>
</div>
);
}
@ -161,13 +163,6 @@ const useStyles = makeStyles(theme => ({
margin: theme.spacing(0.5),
},
},
content: {
height: '100%',
padding: theme.spacing(3, 0),
'&.no-padding': {
padding: '0 0',
},
},
}));
export default PageLayout;

View File

@ -10,7 +10,6 @@ import { TribeQueryVariables } from '@libs/graphql/types';
import { Params, TribeQueryResult } from './types';
import NotFoundPage from '@features/ServerPage/features/NotFoundPage/NotFoundPage';
import PageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Spinner from '@common/Spinner/Spinner';
export interface Props {
@ -41,17 +40,16 @@ function Provider({ children }: Props) {
alignItems: 'center',
};
return (
<PageLayout noPadding contentStyle={centerFlex as React.CSSProperties}>
<Spinner
containerProps={{
...centerFlex,
textAlign: 'center',
height: '100%',
paddingY: 5,
}}
description={t('tribePageContextProvider.loadingTribe')}
/>
</PageLayout>
<Spinner
containerProps={{
...centerFlex,
textAlign: 'center',
minHeight: 'inherit',
height: '100%',
paddingY: 5,
}}
description={t('tribePageContextProvider.loadingTribe')}
/>
);
}

View File

@ -7,7 +7,6 @@ import { SERVER_PAGE } from '@config/namespaces';
import { Container } from '@material-ui/core';
import NotFoundPage from '@features/ServerPage/features/NotFoundPage/NotFoundPage';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Spinner from '@common/Spinner/Spinner';
import PageLayout from './components/PageLayout/PageLayout';
import Ennoblements from './components/Ennoblements/Ennoblements';
@ -28,20 +27,15 @@ function VillagePage() {
alignItems: 'center',
};
return (
<ServerPageLayout
noPadding
contentStyle={centerFlex as React.CSSProperties}
>
<Spinner
containerProps={{
...centerFlex,
textAlign: 'center',
height: '100%',
paddingY: 5,
}}
description={t('loadingVillage')}
/>
</ServerPageLayout>
<Spinner
containerProps={{
...centerFlex,
textAlign: 'center',
minHeight: 'inherit',
paddingY: 5,
}}
description={t('loadingVillage')}
/>
);
}

View File

@ -6,7 +6,7 @@ import { Village } from '../../types';
import { makeStyles } from '@material-ui/core/styles';
import { Toolbar, Typography } from '@material-ui/core';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Content from '@common/Content/Content';
import PlayerProfileLink from '@features/ServerPage/common/PlayerProfileLink/PlayerProfileLink';
import background from './backgrounds/bg-1-dark.jpg';
@ -21,7 +21,7 @@ export interface Props {
function PageLayout({ children, village, server, t }: Props) {
const classes = useStyles();
return (
<ServerPageLayout noPadding>
<div>
<header className={classes.header}>
<Toolbar className={classes.toolbar}>
<div>
@ -40,8 +40,10 @@ function PageLayout({ children, village, server, t }: Props) {
</div>
</Toolbar>
</header>
<div className={classes.content}>{children}</div>
</ServerPageLayout>
<Content minHeight={false} component="div">
{children}
</Content>
</div>
);
}
@ -68,13 +70,6 @@ const useStyles = makeStyles(theme => ({
marginBottom: theme.spacing(1),
},
},
content: {
height: '100%',
padding: theme.spacing(3, 0),
'&.no-padding': {
padding: '0 0',
},
},
}));
export default PageLayout;

View File

@ -18,7 +18,7 @@ import {
Button,
} from '@material-ui/core';
import DateTimePicker from '@common/Picker/DateTimePicker';
import ServerPageLayout from '@features/ServerPage/common/PageLayout/PageLayout';
import Content from '@common/Content/Content';
import Spinner from '@common/Spinner/Spinner';
import Card from './components/Card/Card';
import OneSide from './components/OneSide/OneSide';
@ -205,127 +205,127 @@ function WarStatsPage() {
setIsSubmitting(false);
};
if (loading) {
return (
<Spinner
containerProps={{
minHeight: 'inherit',
textAlign: 'center',
paddingY: 5,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
description={t('loading')}
/>
);
}
return (
<ServerPageLayout>
{loading && (
<Spinner
containerProps={{
minHeight: 'inherit',
textAlign: 'center',
paddingY: 5,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
}}
description={t('loading')}
/>
)}
{!loading && (
<Container>
<form onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Card>
<Typography variant="h4" align="center" gutterBottom>
{t('sections.settings')}
</Typography>
<div className={classes.formGroup}>
{[
{
name: 'ennobledAtGTE',
val: query.ennobledAtGTE,
maxDate: query.ennobledAtLTE,
},
{
name: 'ennobledAtLTE',
val: query.ennobledAtLTE,
minDate: query.ennobledAtGTE,
},
].map(({ name, val, maxDate, minDate }) => {
return (
<DateTimePicker
key={name}
maxDate={maxDate}
minDate={minDate}
disableFuture
disabled={isSubmitting}
TextFieldComponent={props => {
return (
<TextField
{...props}
helperText=""
fullWidth
variant="standard"
/>
);
}}
label={t('inputLabels.' + name)}
value={val}
onChange={d => {
setQuery({ [name]: d ? d : undefined });
}}
/>
);
})}
</div>
</Card>
<Card>
<OneSide
title={t('sections.sideOne')}
players={sideOnePlayers}
tribes={sideOneTribes}
onChangePlayers={sideOneHandleChangePlayers}
onChangeTribes={sideOneHandleChangeTribes}
server={server.key}
tribeIDNEQ={selectedTribesIDs}
playerIDNEQ={selectedPlayersIDs}
className={classes.formGroup}
disabled={isSubmitting}
/>
</Card>
<Card>
<OneSide
title={t('sections.sideTwo')}
players={sideTwoPlayers}
tribes={sideTwoTribes}
onChangePlayers={sideTwoHandleChangePlayers}
onChangeTribes={sideTwoHandleChangeTribes}
server={server.key}
tribeIDNEQ={selectedTribesIDs}
playerIDNEQ={selectedPlayersIDs}
className={classes.formGroup}
disabled={isSubmitting}
/>
</Card>
<Grid item xs={12}>
<Typography align="center" component="div">
<Button
type="submit"
size="large"
color="secondary"
variant="contained"
disabled={
isSubmitting ||
(sideOnePlayers.length === 0 &&
sideOneTribes.length === 0) ||
(sideTwoPlayers.length === 0 &&
sideTwoTribes.length === 0)
}
>
{t('buttons.generateStats')}
</Button>
</Typography>
</Grid>
{results && (
<Grid item xs={12}>
<Results data={results} server={server.key} />
</Grid>
)}
<Content component="div" minHeight={false}>
<Container>
<form onSubmit={handleSubmit}>
<Grid container spacing={2}>
<Card>
<Typography variant="h4" align="center" gutterBottom>
{t('sections.settings')}
</Typography>
<div className={classes.formGroup}>
{[
{
name: 'ennobledAtGTE',
val: query.ennobledAtGTE,
maxDate: query.ennobledAtLTE,
},
{
name: 'ennobledAtLTE',
val: query.ennobledAtLTE,
minDate: query.ennobledAtGTE,
},
].map(({ name, val, maxDate, minDate }) => {
return (
<DateTimePicker
key={name}
maxDate={maxDate}
minDate={minDate}
disableFuture
disabled={isSubmitting}
TextFieldComponent={props => {
return (
<TextField
{...props}
helperText=""
fullWidth
variant="standard"
/>
);
}}
label={t('inputLabels.' + name)}
value={val}
onChange={d => {
setQuery({ [name]: d ? d : undefined });
}}
/>
);
})}
</div>
</Card>
<Card>
<OneSide
title={t('sections.sideOne')}
players={sideOnePlayers}
tribes={sideOneTribes}
onChangePlayers={sideOneHandleChangePlayers}
onChangeTribes={sideOneHandleChangeTribes}
server={server.key}
tribeIDNEQ={selectedTribesIDs}
playerIDNEQ={selectedPlayersIDs}
className={classes.formGroup}
disabled={isSubmitting}
/>
</Card>
<Card>
<OneSide
title={t('sections.sideTwo')}
players={sideTwoPlayers}
tribes={sideTwoTribes}
onChangePlayers={sideTwoHandleChangePlayers}
onChangeTribes={sideTwoHandleChangeTribes}
server={server.key}
tribeIDNEQ={selectedTribesIDs}
playerIDNEQ={selectedPlayersIDs}
className={classes.formGroup}
disabled={isSubmitting}
/>
</Card>
<Grid item xs={12}>
<Typography align="center" component="div">
<Button
type="submit"
size="large"
color="secondary"
variant="contained"
disabled={
isSubmitting ||
(sideOnePlayers.length === 0 &&
sideOneTribes.length === 0) ||
(sideTwoPlayers.length === 0 && sideTwoTribes.length === 0)
}
>
{t('buttons.generateStats')}
</Button>
</Typography>
</Grid>
</form>
</Container>
)}
</ServerPageLayout>
{results && (
<Grid item xs={12}>
<Results data={results} server={server.key} />
</Grid>
)}
</Grid>
</form>
</Container>
</Content>
);
}

View File

@ -0,0 +1,25 @@
export function getEncodedValue(
input: string | (string | null)[] | null | undefined,
allowEmptyString?: boolean
): string | null | undefined {
if (!input) {
return undefined;
}
// '' or []
if (
input.length === 0 &&
(!allowEmptyString || (allowEmptyString && input !== ''))
) {
return null;
}
const str = input instanceof Array ? input[0] : input;
if (str == null) {
return str;
}
if (!allowEmptyString && str === '') {
return null;
}
return str;
}