263 lines
11 KiB
TypeScript
263 lines
11 KiB
TypeScript
'use client';
|
|
import React from 'react';
|
|
import {
|
|
Accordion,
|
|
AccordionDetails,
|
|
AccordionSummary,
|
|
Box,
|
|
Container,
|
|
Divider,
|
|
Grid2,
|
|
Link,
|
|
List,
|
|
ListItem,
|
|
Paper,
|
|
Typography,
|
|
Theme,
|
|
} from '@mui/material';
|
|
import { styled } from '@mui/material/styles';
|
|
import { useTheme } from '@mui/material/styles';
|
|
import { ArrowBack, ExpandMore } from '@mui/icons-material';
|
|
import { IconUrl } from '@/src/core/api';
|
|
import { IMod } from '@/src/types/Imod';
|
|
import { Irace } from '@/src/types/Irace';
|
|
import '@/src/css/Unit.css';
|
|
import { IRaceUnits, IUnitShort } from '@/src/types/IUnitShort';
|
|
import { IBuildingShort, IRaceBuildings } from '@/src/types/IBuildingShort';
|
|
import { StyledLink } from '@/src/commons/StyledLink';
|
|
import { BackButton } from '@/src/commons/BackButton';
|
|
|
|
const SectionTitle = styled(Typography)(({ theme }) => ({
|
|
fontWeight: 700,
|
|
mb: 2,
|
|
fontSize: '1.25rem',
|
|
}));
|
|
|
|
const UnitCard = styled(Paper)(({ theme }) => ({
|
|
background: theme.palette.mode === 'dark'
|
|
? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)'
|
|
: 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)',
|
|
border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`,
|
|
borderRadius: '16px',
|
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
'&:hover': {
|
|
boxShadow: theme.palette.mode === 'dark' ? '0 20px 40px rgba(255, 255, 255, 0.1)' : '0 20px 40px rgba(0, 0, 0, 0.2)',
|
|
borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)',
|
|
},
|
|
}));
|
|
|
|
const BuildingCard = styled(UnitCard)(({ theme }) => ({
|
|
padding: theme.spacing(1),
|
|
}));
|
|
|
|
const UnitLink = styled(Link)(({ theme }) => ({
|
|
color: theme.palette.text.primary,
|
|
textDecoration: 'none',
|
|
display: 'block',
|
|
'&:hover': { color: theme.palette.mode === 'dark' ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)' }
|
|
}));
|
|
|
|
const LoadingText = styled(Typography)(({ theme }) => ({
|
|
color: theme.palette.text.secondary,
|
|
}));
|
|
|
|
function Unit(unit: IUnitShort, modId: number, raceId: string, theme: Theme) { const isDark = theme.palette.mode === 'dark';
|
|
|
|
return (
|
|
<UnitLink href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id}>
|
|
<ListItem sx={{
|
|
color: theme.palette.text.primary,
|
|
padding: '1.5rem 2rem',
|
|
marginBottom: '0.5rem',
|
|
background: isDark
|
|
? 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)'
|
|
: 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)',
|
|
borderRadius: '16px',
|
|
border: `1px solid ${isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`,
|
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
'&:hover': {
|
|
boxShadow: isDark ? '0 20px 40px rgba(255, 255, 255, 0.1)' : '0 20px 40px rgba(0, 0, 0, 0.2)',
|
|
borderColor: isDark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)',
|
|
}
|
|
}}>
|
|
{unit.icon && <img className="unitIcon" src={IconUrl + unit.icon.replaceAll('\\', '/')} />}
|
|
{unit.name}
|
|
{unit.canDetect && <span> <img style={{ verticalAlign: "top" }}
|
|
src="/images/DETECT_YES.webp" /></span>}
|
|
</ListItem>
|
|
</UnitLink>
|
|
)
|
|
}
|
|
|
|
function UnitSmall(unit: IUnitShort, modId: number, raceId: string) {
|
|
var unitName = ""
|
|
if (unit.name.length > 21) {
|
|
unitName = unit.name.substring(0, 19) + "...";
|
|
} else {
|
|
unitName = unit.name;
|
|
}
|
|
|
|
return (<StyledLink href={"/mod/" + modId + "/race/" + raceId + "/unit/" + unit.id} >
|
|
{unit.icon && <img className="unitIconSmall" src={IconUrl + unit.icon.replaceAll('\\', '/')} />}
|
|
<span style={{ fontSize: 14 }}>{unitName}</span>
|
|
{unit.canDetect && <span> <img
|
|
src="/images/DETECT_YES.webp" /></span>}<br /></StyledLink>)
|
|
}
|
|
|
|
function Building(building: IBuildingShort, modId: number, raceId: string, theme: Theme) {
|
|
const isDark = theme.palette.mode === 'dark';
|
|
|
|
return (<Grid2 size={{ xs: 12, md: 3 }}>
|
|
<Link href={"/mod/" + modId + "/race/" + raceId + "/building/" + building.id} sx={{
|
|
textDecoration: 'none',
|
|
display: 'block',
|
|
'&:hover': { color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)' }
|
|
}}>
|
|
<BuildingCard elevation={0}>
|
|
<ListItem sx={{}}>
|
|
{building.icon && <img className="unitIcon" src={IconUrl + building.icon.replaceAll('\\', '/')} />}
|
|
<StyledLink>{building.name}</StyledLink>
|
|
{building.canDetect && <span> <img style={{ verticalAlign: "top" }}
|
|
src="/images/DETECT_YES.webp" /></span>}
|
|
</ListItem>
|
|
{building.units.map(unit => UnitSmall(unit, modId, raceId))}
|
|
</BuildingCard>
|
|
</Link>
|
|
</Grid2>)
|
|
}
|
|
|
|
interface UnitsProps {
|
|
raceId: string;
|
|
modId: number;
|
|
units: IRaceUnits | null;
|
|
buildings: IRaceBuildings | null;
|
|
}
|
|
|
|
function Units({ raceId, modId, units, buildings }: UnitsProps) {
|
|
const theme = useTheme();
|
|
const isDark = theme.palette.mode === 'dark';
|
|
|
|
if (units) {
|
|
const accordionSx = isDark ? {
|
|
background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)',
|
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
borderRadius: '12px',
|
|
'&:before': { display: 'none' },
|
|
'&.Mui-expanded': { margin: '0 0 8px 0' },
|
|
} : {
|
|
background: 'linear-gradient(145deg, #ffffff 0%, #f5f5f5 100%)',
|
|
border: '1px solid rgba(0, 0, 0, 0.1)',
|
|
borderRadius: '12px',
|
|
'&:before': { display: 'none' },
|
|
'&.Mui-expanded': { margin: '0 0 8px 0' },
|
|
};
|
|
|
|
return (buildings != null ?
|
|
<Box>
|
|
<Grid2 container spacing={2}>
|
|
{buildings.buildings.map(building => Building(building, modId, raceId, theme))}
|
|
</Grid2>
|
|
{buildings.buildingsAdvanced.length > 0 && <Box sx={{ mt: 3 }}>
|
|
<Divider sx={{ my: 2, borderColor: isDark ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)' }} />
|
|
<SectionTitle>Advanced buildings</SectionTitle>
|
|
<Grid2 container spacing={2}>
|
|
{buildings.buildingsAdvanced.map(building => Building(building, modId, raceId, theme))}
|
|
</Grid2><br /></Box>}
|
|
<Box sx={{ mt: 3 }}>
|
|
<Accordion sx={accordionSx}>
|
|
<AccordionSummary
|
|
expandIcon={<ExpandMore sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)' }} />}
|
|
aria-controls="units-accordion"
|
|
id="units-accordion"
|
|
sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', '& .MuiAccordionSummary-content': { margin: '12px 0' }, '& .MuiAccordionSummary-content.Mui-expanded': { margin: '12px 0' } }}
|
|
>
|
|
<SectionTitle sx={{ mb: 0, display: 'flex', alignItems: 'center' }}>All units</SectionTitle>
|
|
</AccordionSummary>
|
|
<AccordionDetails>
|
|
<Grid2 container spacing={2}>
|
|
<Grid2 size={{ xs: 12, md: 4 }}>
|
|
<Typography variant="h6" sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', fontWeight: 600, mb: 2 }}>Infantry</Typography>
|
|
{units.infantry.map(unit => Unit(unit, modId, raceId, theme))}
|
|
</Grid2>
|
|
<Grid2 size={{ xs: 12, md: 4 }}>
|
|
<Typography variant="h6" sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', fontWeight: 600, mb: 2 }}>Tech</Typography>
|
|
<List>
|
|
{units.tech.map(unit => Unit(unit, modId, raceId, theme))}
|
|
</List>
|
|
</Grid2>
|
|
<Grid2 size={{ xs: 12, md: 4 }}>
|
|
<Typography variant="h6" sx={{ color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', fontWeight: 600, mb: 2 }}>Support</Typography>
|
|
<List>
|
|
{units.support.map(unit => Unit(unit, modId, raceId, theme))}
|
|
</List>
|
|
</Grid2>
|
|
</Grid2>
|
|
</AccordionDetails>
|
|
</Accordion>
|
|
</Box>
|
|
</Box> : <LoadingText>Loading</LoadingText>
|
|
)
|
|
} else {
|
|
return <LoadingText>Loading...</LoadingText>;
|
|
}
|
|
}
|
|
|
|
interface RacePageClientProps {
|
|
initialMod: IMod | null;
|
|
initialRace: Irace | null;
|
|
raceUnits: IRaceUnits | null;
|
|
raceBuildings: IRaceBuildings | null;
|
|
}
|
|
|
|
export default function RacePageClient({ initialMod, initialRace, raceUnits, raceBuildings }: RacePageClientProps) {
|
|
const theme = useTheme();
|
|
|
|
if (initialMod != null && initialRace != null) {
|
|
const backRef = "/mod/" + initialMod.id;
|
|
const isDark = theme.palette.mode === 'dark';
|
|
|
|
return (
|
|
<Container maxWidth="lg">
|
|
<BackButton
|
|
variant="outlined"
|
|
startIcon={<ArrowBack />}
|
|
href={backRef}
|
|
>
|
|
Back to mod
|
|
</BackButton>
|
|
|
|
<Box sx={{ mb: 4 }}>
|
|
<Typography variant="h3" component="h1" sx={{
|
|
fontWeight: 800,
|
|
mb: 1,
|
|
background: isDark
|
|
? 'linear-gradient(135deg, #dee2e6 0%, #e0e0e0 100%)'
|
|
: 'none',
|
|
WebkitBackgroundClip: isDark ? 'text' : 'initial',
|
|
WebkitTextFillColor: isDark ? '#dee2e6' : 'initial',
|
|
color: isDark ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)',
|
|
}}>
|
|
{initialRace.name}
|
|
</Typography>
|
|
<Typography variant="h6" sx={{
|
|
color: theme.palette.text.secondary,
|
|
fontWeight: 500,
|
|
}}>
|
|
{initialMod.name} ({initialMod.version})
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Units raceId={initialRace.id} modId={initialMod.id} units={raceUnits} buildings={raceBuildings} />
|
|
</Container>
|
|
);
|
|
} else {
|
|
return (
|
|
<Container maxWidth="lg">
|
|
<LoadingText variant="h5" sx={{ py: 8, textAlign: 'center' }}>
|
|
Race not found
|
|
</LoadingText>
|
|
</Container>
|
|
);
|
|
}
|
|
}
|