diff --git a/README.md b/README.md index 0d741f5..92b5aa3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Soulstorm wiki frontend -## Стек технологий: +## Tech stack: -- react (вперемешку классы и функции) +- react (mix of classes and functions) - typescript \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 75d796c..bc0a16a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,66 +1,106 @@ import './App.css'; import React from "react"; -import {AppBar, Container, createTheme, IconButton, ThemeProvider, Toolbar, Typography} from "@mui/material"; -import {MyRoutes} from "./Routes"; +import { + AppBar, + Box, + Container, + createTheme, + ThemeProvider, + Toolbar, + Typography, + useTheme +} from "@mui/material"; +import { MyRoutes } from "./Routes"; +import { styled } from '@mui/material/styles'; + +// Styled components +const StyledAppBar = styled(AppBar)(({ theme }) => ({ + background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)', + boxShadow: '0 4px 20px rgba(0, 0, 0, 0.3)', + borderBottom: '1px solid rgba(255, 255, 255, 0.1)', +})); + +const LogoText = styled(Typography)(({ theme }) => ({ + fontSize: '1.5rem', + fontWeight: 800, + background: 'linear-gradient(135deg, #e94560 0%, #ff6b6b 100%)', + WebkitBackgroundClip: 'text', + WebkitTextFillColor: 'transparent', + letterSpacing: '-0.5px', + cursor: 'pointer', + '&:hover': { + transform: 'scale(1.05)', + transition: 'transform 0.2s ease', + }, +})); function App() { - console.log(process.env); + const theme = useTheme(); - const steamLogin = (event: React.MouseEvent) => { - (document.getElementById('submit-steam-login') as HTMLFormElement).click() - }; - - const theme = createTheme({ + const themeConfig = createTheme({ + breakpoints: { + values: { + xs: 0, + sm: 600, + md: 900, + lg: 1320, + xl: 1536, + }, + }, palette: { primary: { - main: "#191919", + main: "#1a1a2e", + }, + background: { + default: '#0a0a0a', + paper: '#1a1a2e', + }, + }, + typography: { + fontFamily: '"Inter", "Roboto", "Helvetica", sans-serif', + }, + shape: { + borderRadius: 12, + }, + components: { + MuiTableCell: { + styleOverrides: { + root: { + color: 'rgba(255, 255, 255, 0.85)', + }, + }, }, }, }); + return ( + + + + + + + {/* Left side - Logo */} + + + 🎮 Autogeneration wiki + + - return ( -
- - - - - - - Autogeneration wiki - - {/**/} - - - - - - - + {/* Right side - Navigation */} + + + + + + -
- - - - - - - - -
-
- ); + + + +
+
+ ); } export default App; \ No newline at end of file diff --git a/src/classes/Ability.tsx b/src/classes/Ability.tsx index cb84ad4..a2b0843 100644 --- a/src/classes/Ability.tsx +++ b/src/classes/Ability.tsx @@ -6,7 +6,15 @@ import {getIcon} from "./ModifiersProvideTable"; import {IMod} from "../types/Imod"; import AbilityFull from "./AbilityFull"; import {Irace} from "../types/Irace"; +import {styled} from '@mui/material/styles'; +const StyledAccordion = styled(Accordion)(({ theme }) => ({ + 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' }, +})); interface IAbilityProps { ability: IAbilityShort, @@ -18,22 +26,23 @@ function Ability(props: IAbilityProps){ const ability = props.ability - return
+ return
} + expandIcon={} aria-controls="panel1-content" + sx={{color: '#ffffff'}} > - + {ability.activationType === "Passive ability" ? : }   {ability.name ? ability.name : ability.fileName.charAt(0).toUpperCase() + ability.fileName.slice(1).replaceAll('_', ' ').replace('.rgd', '')} - ({ability.activationType}) + ({ability.activationType}) - + -
+
} diff --git a/src/classes/AbilityFull.tsx b/src/classes/AbilityFull.tsx index 4f3ec8d..afd4062 100644 --- a/src/classes/AbilityFull.tsx +++ b/src/classes/AbilityFull.tsx @@ -27,6 +27,7 @@ import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import ability from "./Ability"; import AbilitySpawnedObject from "./AbilitySpawnedObject"; import DpsTable from "./DpsTable"; +import {DescriptionBox} from "../commons/DescriptionBox"; interface IAbilityFullState { ability?: IAbilityFill; @@ -41,16 +42,34 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race const StyledTableCell = styled(TableCell)(({theme}) => ({ [`&.${tableCellClasses.head}`]: { - backgroundColor: "rgb(244,244,244)", + backgroundColor: 'rgba(233, 69, 96, 0.15)', + color: '#ffffff', + fontWeight: 700, marginRight: 'auto', marginLeft: 'auto', - paddingLeft: 10 + paddingLeft: 10, + borderBottom: '2px solid rgba(233, 69, 96, 0.3)', }, [`&.${tableCellClasses.body}`]: { fontSize: 12, textAlign: 'center', + color: 'rgba(255, 255, 255, 0.85)', paddingRight: 18, - paddingLeft: 10 + paddingLeft: 10, + }, + })); + + const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.9)', + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, }, })); @@ -90,21 +109,21 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race const showTargetTable = !showDpsTable && ability.abilityEnvironment == null && (ability.targetFilter.length > 0 || ability.radius !== 0) return ( -
+
{ability.initialDelayTime !== undefined && props.isChild === true && -
 After {ability.initialDelayTime.toFixed(1)}s  +
 After {ability.initialDelayTime.toFixed(1)}s 
} - + - - Affected on - {ability.areaFilter} + + Affected on + {ability.areaFilter} {props.isChild !== true && (ability.costPower !== undefined && ability.costPower > 0 || ability.costRequisition !== undefined && ability.costRequisition > 0 || @@ -113,8 +132,8 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race - Cost - + Cost + {ability.costRequisition !== undefined && ability.costRequisition > 0 &&    @@ -135,19 +154,19 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race {props.isChild !== true && ability.rechargeTime !== undefined && ability.rechargeTime > 0 && ability.activation !== "On/off ability" && - Recharge time - + Recharge time +   {ability.rechargeTime}s  - {ability.rechargeTimerGlobal && global cooldown} + {ability.rechargeTimerGlobal && global cooldown} } {props.isChild !== true && ability.initialDelayTime !== undefined && ability.initialDelayTime > 0 && - Delay after cast - + Delay after cast +   {ability.initialDelayTime.toFixed(1)}s @@ -156,8 +175,8 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race } {ability.durationTime !== undefined && ability.durationTime > 1.5 && - Duration time - + Duration time +   {ability.durationTime.toFixed(1)}s @@ -166,16 +185,16 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race } {ability.areaEffect !== null && ability.radius !== undefined && ability.radius > 0 && - Radius - - {ability.radius.toFixed(0)} {ability.areaEffect} + Radius + + {ability.radius.toFixed(0)} {ability.areaEffect} } {props.isChild !== true && ability.range !== undefined && ability.areaFilter !== "Own" && ability.range > 0 && - Range - + Range + {ability.range.toFixed(0)} @@ -184,26 +203,20 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race - Throw force - {ability.throwForceMin != 0 ? ability.throwForceMin + " - " + ability.throwForceMax : "-"} + Throw force + {ability.throwForceMin != 0 ? ability.throwForceMin + " - " + ability.throwForceMax : "-"} } - - -

-
- {props.isChild !== true && ability.description} -
+ {props.isChild !== true && {ability.description}}
{showDpsTable ? - {(ability.refreshTime !== undefined && ability.durationTime !== undefined && ability.refreshTime * 2 < ability.durationTime) && Every {ability.refreshTime.toFixed(1)}s deal damege:} + {(ability.refreshTime !== undefined && ability.durationTime !== undefined && ability.refreshTime * 2 < ability.durationTime) && Every {ability.refreshTime.toFixed(1)}s deal damege:} : showTargetTable ? @@ -235,7 +248,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race - + {isAbilityApplyTo(ArmorTypeNames.InfantryLow)} {isAbilityApplyTo(ArmorTypeNames.InfantryMedium)} {isAbilityApplyTo(ArmorTypeNames.InfantryHigh)} @@ -272,7 +285,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race - + {isAbilityApplyTo(ArmorTypeNames.VehicleLow)} {isAbilityApplyTo(ArmorTypeNames.VehicleMedium)} {isAbilityApplyTo(ArmorTypeNames.VehicleHigh)} @@ -321,7 +334,7 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race - + {isAbilityApplyTo(ArmorTypeNames.InfantryLow)} {isAbilityApplyTo(ArmorTypeNames.InfantryMedium)} {isAbilityApplyTo(ArmorTypeNames.InfantryHigh)} @@ -352,10 +365,10 @@ export default function AbilityFull(props: { abilityId?: number, mod: IMod, race } - {ability.activation !== "Passive ability" && props.isChild !== true && Hotkey: {ability.hotkey}} + {props.isChild !== true && ability.activation !== "Passive ability" && Hotkey: {ability.hotkey}}
) - } else return
loading...
; + } else return
loading...
; diff --git a/src/classes/AbilitySpawnedObject.tsx b/src/classes/AbilitySpawnedObject.tsx index 4060721..d0cfd5b 100644 --- a/src/classes/AbilitySpawnedObject.tsx +++ b/src/classes/AbilitySpawnedObject.tsx @@ -6,7 +6,8 @@ import { TableBody, TableCell, TableContainer, TableRow, - Tooltip + Tooltip, + Typography } from "@mui/material"; import {ExpandMore} from "@mui/icons-material"; import React from "react"; @@ -21,6 +22,29 @@ import ArmorType from "./ArmorType"; import Vision from "./Vision"; import Ability from "./Ability"; import DeathExplosion from "./DeathExplosion"; +import {styled} from '@mui/material/styles'; +import {DescriptionBox} from "../commons/DescriptionBox"; + +const SectionTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 700, + mb: 2, + color: '#ffffff', + fontSize: '1.25rem', +})); + +const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.9)', + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, + }, +})); interface AbilityEnvironmentProps { @@ -33,21 +57,22 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) { const abilityEnvironment = props.abilityEnvironment - return
-

{props.abilityEnvironment.name ?? props.abilityEnvironment.filename.charAt(0).toUpperCase() + props.abilityEnvironment.filename.slice(1).replaceAll('_', ' ').replace('.rgd', '')} - (Spawned object) -

+ return
+ + {props.abilityEnvironment.name ?? props.abilityEnvironment.filename.charAt(0).toUpperCase() + props.abilityEnvironment.filename.slice(1).replaceAll('_', ' ').replace('.rgd', '')} + (Spawned object) + - + {abilityEnvironment.armorType !== null && - Armor type - + Armor type + {abilityEnvironment.armorType2 == null ? : - Health - + Health +   {abilityEnvironment.health} {abilityEnvironment.healthRegeneration != null && abilityEnvironment.healthRegeneration > 0 && @@ -77,18 +102,16 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) { - Move speed - - {abilityEnvironment.moveSpeed} - + Move speed + {abilityEnvironment.moveSpeed} } {abilityEnvironment.sightRadius != null && - Vision - + Vision + @@ -96,8 +119,8 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) { } {abilityEnvironment.lifetime != null && - Lifetime - + Lifetime +   {abilityEnvironment.lifetime.toFixed(0)}s  @@ -109,9 +132,9 @@ function AbilitySpawnedObject(props: AbilityEnvironmentProps) { -
+ {abilityEnvironment.description} -
+
{abilityEnvironment.abilities.length > 0 && {abilityEnvironment.abilities.map(a => diff --git a/src/classes/DeathExplosion.tsx b/src/classes/DeathExplosion.tsx index 4486b0c..73151ae 100644 --- a/src/classes/DeathExplosion.tsx +++ b/src/classes/DeathExplosion.tsx @@ -24,6 +24,29 @@ import {IDeathExplosion} from "../types/IDeathExplosion"; import Required from "./Required"; import ArmorTypeNames from "../types/ArmorTypeValues"; import DpsTable from "./DpsTable"; +import {styled} from '@mui/material/styles'; + +const StyledAccordion = styled(Accordion)(({ theme }) => ({ + 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' }, +})); + +const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'rgba(255, 255, 255, 0.03)', + border: '1px solid rgba(255, 255, 255, 0.05)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.85)', + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, + }, +})); interface DeathExpProps { @@ -37,44 +60,37 @@ function DeathExplosion(props: DeathExpProps) { const chanceTotalNotZero = deathExplosion.globalChance * deathExplosion.chance != 0 return chanceTotalNotZero ? -
+
} + expandIcon={} aria-controls="panel1-content" + sx={{color: '#ffffff'}} > - + Death Explosion - + - +
- Chance - - {deathExplosion.globalChance * deathExplosion.chance}% - + Chance + {deathExplosion.globalChance * deathExplosion.chance}% - Damage radius - - {deathExplosion.damageRadius} - + Damage radius + {deathExplosion.damageRadius} - - Throw force - - {deathExplosion.throwForceMin} - {deathExplosion.throwForceMax} - + + Throw force + {deathExplosion.throwForceMin} - {deathExplosion.throwForceMax}
@@ -88,7 +104,7 @@ function DeathExplosion(props: DeathExpProps) {
-
: +
: } diff --git a/src/classes/DpsTable.tsx b/src/classes/DpsTable.tsx index fa59665..5bba4f3 100644 --- a/src/classes/DpsTable.tsx +++ b/src/classes/DpsTable.tsx @@ -19,16 +19,20 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi const StyledTableCell = styled(TableCell)(({theme}) => ({ [`&.${tableCellClasses.head}`]: { - backgroundColor: "rgb(244,244,244)", + backgroundColor: 'rgba(233, 69, 96, 0.15)', + color: '#ffffff', + fontWeight: 700, marginRight: 'auto', marginLeft: 'auto', - paddingLeft: 10 + paddingLeft: 10, + borderBottom: '2px solid rgba(233, 69, 96, 0.3)', }, [`&.${tableCellClasses.body}`]: { fontSize: 12, textAlign: 'center', + color: 'rgba(255, 255, 255, 0.85)', paddingRight: 18, - paddingLeft: 10 + paddingLeft: 10, }, })); @@ -158,7 +162,7 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi - + {getTotalDamage(vehLowPiercing)} {getTotalDamage(vehMedPiercing)} {getTotalDamage(vehHighPiercing)} @@ -212,7 +216,7 @@ export default function DpsTable(props: { mod: IMod, minDamageValue?: number, mi - + {getTotalDamage(infLowPiercing)} {getTotalDamage(infMedPiercing)} {getTotalDamage(infHighPiercing)} diff --git a/src/classes/ModifiersProvideTable.tsx b/src/classes/ModifiersProvideTable.tsx index 7892d19..b73e353 100644 --- a/src/classes/ModifiersProvideTable.tsx +++ b/src/classes/ModifiersProvideTable.tsx @@ -6,6 +6,20 @@ import {Irace} from "../types/Irace"; import {IAffectedData} from "../types/IAffectedData"; import {IMod} from "../types/Imod"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; +import {styled} from '@mui/material/styles'; +import {StyledLink} from "../commons/StyledLink"; + +// Styled components for dark theme +const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.85)', + }, +})); + interface IModifiersProvidesTable { modifiers: IModifier[], @@ -142,15 +156,14 @@ export function ModifiersProvidesAddonTable(props: IModifiersProvidesTable) { let noWeaponMods = props.modifiers.filter(m => !m.reference.includes("default_weapon_modifier_hardpoint")) return ( - + {noWeaponMods.map(m => - {getModName(m.reference)} - {getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} + {getModName(m.reference)} + {getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} )}
@@ -168,37 +181,37 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) { var affectedUnit = props.affectedData.affectedUnits.find(au => au.filename.replaceAll('.rgd', '').split(";")[0] === target || au.filename.replaceAll('.rgd', '').split(";")[1] === target); if (affectedUnit != null) { return {affectedUnit.name} - + } var affectedSergeant = props.affectedData.affectedSergeants.find(as => as.filename.replaceAll('.rgd', '') === target); if (affectedSergeant != null) { return {affectedSergeant.name} at {affectedSergeant.unit.name} - + } var affectedBuilding = props.affectedData.affectedBuildings.find(ab => ab.filename.replaceAll('.rgd', '') === target); if (affectedBuilding != null) { - return {affectedBuilding.name} - + } var affectedWeapon = props.affectedData.affectedWeapons.find(aw => aw.filename.replaceAll('.rgd', '') === target); if (affectedWeapon != null) { return {affectedWeapon.name ? affectedWeapon.name : affectedWeapon.filename.replaceAll('_', ' ').replace('.rgd', '')} at { affectedWeapon.units.map(unit => - {affectedWeapon != null && affectedWeapon.units.length + affectedWeapon.sergeants.length > 1 ?
: }{affectedWeapon != null && affectedWeapon.units.length + affectedWeapon.sergeants.length > 1 ?
: } {unit.name} -
+
) } { @@ -206,18 +219,18 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) { {affectedWeapon != null && affectedWeapon.units.length + affectedWeapon.sergeants.length > 1 ?
: }{affectedSergeant.name} at {affectedSergeant.unit.name} -
+ ) } { affectedWeapon.buildings.map(affectedBuilding => - {affectedBuilding.name} - + ) } @@ -228,23 +241,21 @@ export function ModifiersProvidesTable(props: IModifiersProvidesResearchTable) { } return ( - + {mods.map(m => - {getTarget(m.target)} - {getModName(m.reference)} - {getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} + {getTarget(m.target)} + {getModName(m.reference)} + {getModIcon(m.reference)} {getChangeDescription(m.usageType, m.value)} {m.maxLifeTime !== undefined && m.maxLifeTime > 0 && {m.maxLifeTime}s } - {m.exclusive ? " does not stuck" : " does stuck"} + {m.exclusive ? " does not stack" : " does stack"} {m.probabilityOfApplying !== undefined && m.probabilityOfApplying !== 1.0 && - ({m.probabilityOfApplying * 100}% chance) + ({m.probabilityOfApplying * 100}% chance) } diff --git a/src/classes/Required.tsx b/src/classes/Required.tsx index 7fe3d0c..ea29d11 100644 --- a/src/classes/Required.tsx +++ b/src/classes/Required.tsx @@ -5,6 +5,7 @@ import {IconUrl} from "../core/api"; import {IResearchRequirements} from "../types/IResearchRequirements"; import {IRequirement, IRequirementLimitByBuildingDto, IRequirementSquadsDto} from "../types/IRequirement"; import {IUnitShort} from "../types/IUnitShort"; +import {StyledLink} from "../commons/StyledLink"; interface IRequiredProps{ requirement: IRequirement, @@ -20,9 +21,9 @@ function Required (props: IRequiredProps) {   Global addon: -   +   {rga.name} - + )} } @@ -30,29 +31,29 @@ function Required (props: IRequiredProps) { return requirementBuildings.length > 0 && (
  Buildings: -  {" "} +  {" "} {requirementBuildings[0].name} - {requirementBuildings[1] !== undefined && or   {requirementBuildings[1] !== undefined && or   -  {" "} - {requirementBuildings[1].name} }
+  {" "} + {requirementBuildings[1].name} } ); } function renderRequiremenStructureExclusive(buildingExclusive: IBuildingShort) { return
  Can't be build with: -  {" "} +  {" "} {buildingExclusive.name} -
+ } function renderRequiremenAddonExclusive(addonShort: IBuildingAddonShort) { return
  Can't be build with: -   +   {addonShort.name} -
+ } function renderRequirementOwnership(requirementOwnership: string[],) { @@ -70,8 +71,8 @@ function Required (props: IRequiredProps) { function renderLimitPerBuilding(limitPerBuilding: IRequirementLimitByBuildingDto,) { return   One -  {" "} - {limitPerBuilding.building.name} allow produce max {limitPerBuilding.limit} +  {" "} + {limitPerBuilding.building.name} allow produce max {limitPerBuilding.limit} } function renderRequirementResearches(requirementResearches: IResearchRequirements[],) { @@ -79,9 +80,9 @@ function Required (props: IRequiredProps) {   Research {rs.researchMustNotBeComplete ? "not" : ""}: -   +   {rs.researchShortDto.name} - + )} } @@ -90,9 +91,9 @@ function Required (props: IRequiredProps) {   Squad: {rs.count} -   +   {rs.squad.name} - + )} } @@ -101,12 +102,12 @@ function Required (props: IRequiredProps) { return requirementResearches.length > 0 && ( + } ); } @@ -114,48 +115,48 @@ function Required (props: IRequiredProps) { return ( -
-

Required:

+
+

Required:

{requirement.requiredTotalPop !== undefined && requirement.requiredTotalPop !== null && -
  Population:   Population:   {requirement.requiredTotalPop}
} {requirement.mobBonus !== null && requirement.mobBonus !== undefined && -
  Mob requirement:   Mob requirement:   {requirement.mobBonus.mobvalueRequired} in radius {requirement.mobBonus.proximityRequired}
} {requirement.requireAddon !== null && -
  Addon:   Addon:   - + {requirement.requireAddon.name} - + {requirement.replaceWhenDone && (old provides replace)}
} {requirement.requirementAddonExclusive != null && - renderRequiremenAddonExclusive(requirement.requirementAddonExclusive,)} +
{renderRequiremenAddonExclusive(requirement.requirementAddonExclusive,)}
} {requirement.requirementBuildings.map(b => -
  Building:   Building:   - {b.name}
) + {b.name}
) } {requirement.requirementResearches.length !== 0 && - renderRequirementResearches(requirement.requirementResearches,)} +
{renderRequirementResearches(requirement.requirementResearches,)}
} {requirement.requirementSquads.length !== 0 && - renderRequirementSquads(requirement.requirementSquads,)} +
{renderRequirementSquads(requirement.requirementSquads,)}
} {requirement.requirementResearchesEither.length !== 0 && - renderRequirementResearchesEither(requirement.requirementResearchesEither,)} +
{renderRequirementResearchesEither(requirement.requirementResearchesEither,)}
} {requirement.requirementBuildingsEither.length !== 0 && - renderRequirementBuildingEither(requirement.requirementBuildingsEither,)} +
{renderRequirementBuildingEither(requirement.requirementBuildingsEither,)}
} {requirement.requirementStructureExclusive != null && - renderRequiremenStructureExclusive(requirement.requirementStructureExclusive,)} - {requirement.limitByBuilding !== undefined && requirement.limitByBuilding !== null && renderLimitPerBuilding(requirement.limitByBuilding)} +
{renderRequiremenStructureExclusive(requirement.requirementStructureExclusive,)}
} + {requirement.limitByBuilding !== undefined && requirement.limitByBuilding !== null &&
{renderLimitPerBuilding(requirement.limitByBuilding)}
} {requirement.requirementsGlobalAddons.length !== 0 && - renderRequirementGlobalAddons(requirement.requirementsGlobalAddons,)} - {renderRequirementOwnership(requirement.requiredOwnership)} +
{renderRequirementGlobalAddons(requirement.requirementsGlobalAddons,)}
} +
{renderRequirementOwnership(requirement.requiredOwnership)}
) } diff --git a/src/classes/Sergeant.tsx b/src/classes/Sergeant.tsx index bcaff19..413e49b 100644 --- a/src/classes/Sergeant.tsx +++ b/src/classes/Sergeant.tsx @@ -1,6 +1,6 @@ import React from "react"; import {ISergeant, IShortWeapon} from "../types/IUnit"; -import {Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableRow, Tooltip} from "@mui/material"; +import {Grid2, Paper, Table, TableBody, TableCell, TableContainer, TableRow, Tooltip, Typography} from "@mui/material"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; import ArmorType from "./ArmorType"; import WeaponSlot from "./WeaponSlot"; @@ -12,6 +12,29 @@ import Required from "./Required"; import {ModifiersProvidesTable} from "./ModifiersProvideTable"; import Ability from "./Ability"; import DeathExplosion from "./DeathExplosion"; +import {styled} from '@mui/material/styles'; +import {DescriptionBox} from "../commons/DescriptionBox"; + +const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.9)', + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, + }, +})); + +const SectionTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 700, + mb: 2, + color: '#ffffff', + fontSize: '1.25rem', +})); export interface SergeantProps { @@ -38,17 +61,17 @@ const Sergeant = (props: SergeantProps) => { }) return ( -
+
- +
- Cost - + Cost + {sergeant.buildCostRequisition > 0 &&    @@ -72,7 +95,7 @@ const Sergeant = (props: SergeantProps) => { {(sergeant.requisitionIncome !== undefined && sergeant.requisitionIncome !== null || sergeant.powerIncome !== undefined && sergeant.powerIncome !== null || sergeant.faithIncome !== undefined && sergeant.faithIncome !== null) && - Resource income + Resource income {sergeant.requisitionIncome !== undefined && sergeant.requisitionIncome > 0 &&   { - Armor type - + Armor type + {sergeant.armorType2 == null ? : } @@ -101,8 +124,8 @@ const Sergeant = (props: SergeantProps) => { - Health - + Health +   {sergeant.health} {sergeant.healthRegeneration > 0 && @@ -112,8 +135,8 @@ const Sergeant = (props: SergeantProps) => { - Morale - + Morale + {sergeant.moraleIncomeMax && +{sergeant.moraleIncomeMax} } {sergeant.moraleRate && + {sergeant.moraleRate}/s } @@ -124,22 +147,20 @@ const Sergeant = (props: SergeantProps) => { - Mass - - {sergeant.mass} - + Mass + {sergeant.mass} - Vision - + Vision + {sergeant.mobValue != null && - Mob value + Mob value {sergeant.mobValue} @@ -150,20 +171,20 @@ const Sergeant = (props: SergeantProps) => { -
+ {sergeant.description} -
+
{sergeant.requirements !== null && } {sergeant.modifiers.length > 0 && -

Affected on

+ Affected on
} {sergeant.abilities.length > 0 && -

Abilities

+ Abilities {sergeant.abilities.map(a => )} @@ -185,7 +206,7 @@ const Sergeant = (props: SergeantProps) => { {renderAffectedResearches(sergeant.affectedResearches, props.mod.id, props.race.id)}
- Hotkey: {sergeant.hotkey} + Hotkey: {sergeant.hotkey} ) }; diff --git a/src/classes/UnitsTable.tsx b/src/classes/UnitsTable.tsx index 5bed7e4..bb0e84d 100644 --- a/src/classes/UnitsTable.tsx +++ b/src/classes/UnitsTable.tsx @@ -1,10 +1,48 @@ import React, {useEffect, useState} from "react"; import {AvailableBuildings, AvailableUnits} from "../core/api"; -import {Grid2, Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material"; +import {Grid2, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper} from "@mui/material"; +import {styled} from '@mui/material/styles'; import {IRaceUnits, IUnitShort} from "../types/IUnitShort"; import {IBuildingShort, IRaceBuildings} from "../types/IBuildingShort"; +import {StyledLink} from "../commons/StyledLink"; +// Styled components for dark theme +const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', +})); + +const StyledTable = styled(Table)(({ theme }) => ({ + color: '#ffffff', + '& .MuiTableCell-head': { + backgroundColor: 'rgba(233, 69, 96, 0.15)', + color: '#ffffff', + fontWeight: 700, + fontSize: '1rem', + borderBottom: '2px solid rgba(233, 69, 96, 0.3)', + }, + '& .MuiTableCell-body': { + color: 'rgba(255, 255, 255, 0.85)', + borderBottom: '1px solid rgba(255, 255, 255, 0.05)', + fontSize: '0.9rem', + }, + '& .MuiTableRow-hover:hover': { + backgroundColor: 'rgba(233, 69, 96, 0.08)', + }, +})); + +const StyledHeaderLink = styled(StyledLink)(({ theme }) => ({ + color: '#ffffff', + fontWeight: 700, + fontSize: '1.1rem', + '&:hover': { + color: '#e94560', + }, +})); + interface IUnitsTable { racesUnits: IRaceUnits[] racesBuildings: IRaceBuildings[] @@ -12,6 +50,7 @@ interface IUnitsTable { export default function UnitsTable(prop: {modId: number}) { + const [unitsTable, setUnitsTable] = useState({ racesUnits: [], racesBuildings: [] @@ -19,15 +58,19 @@ export default function UnitsTable(prop: {modId: number}) { function getUnitRef(modId: number, raceId: string, unit: IUnitShort) { - return - {unit.name} -
+ return + + {unit.name} +
+
} function getBuildingRef(modId: number, raceId: string, building: IBuildingShort) { - return - {building.name} -
+ return + + {building.name} +
+
} useEffect(() => { @@ -99,31 +142,31 @@ export default function UnitsTable(prop: {modId: number}) { } function generateRaceUnitTable(racesUnitsPart: IRaceUnits[], racesBuildings: IRaceBuildings[]){ - return ( - {
+ return ( + { - {racesUnitsPart.map(raceUnits =>

{raceUnits.race.name} + {racesUnitsPart.map(raceUnits =>

{raceUnits.race.name}

)} - + {racesUnitsPart.map(raceUnits => {getUnitsRef(raceUnits.infantry.concat(raceUnits.support), raceUnits.race.id)} ) } - + {racesUnitsPart.map(raceUnits => {getUnitsRef(raceUnits.tech, raceUnits.race.id)} ) } - + {racesBuildings.map(raceBuilding => {raceBuilding.buildings.map(building => getBuildingRef(prop.modId, raceBuilding.race.id, building))} @@ -131,8 +174,8 @@ export default function UnitsTable(prop: {modId: number}) { } -

} -
) + } + ) } function generateRaceUnitTables(racesUnits: IRaceUnits[], racesBuildings: IRaceBuildings[], raceCount: number){ @@ -156,13 +199,13 @@ export default function UnitsTable(prop: {modId: number}) { function generateRaceList(unitsTable: IUnitsTable){ return unitsTable.racesUnits.sort(function (a, b) { return a.race.name.localeCompare(b.race.name); - }).map(raceUnits =>
{raceUnits.race.name}
+ }).map(raceUnits =>
{raceUnits.race.name}
) } if(unitsTable.racesUnits.length > 10){ - return + return {generateRaceList(unitsTable)} } else { diff --git a/src/classes/Weapon.tsx b/src/classes/Weapon.tsx index d04169b..ec5d72c 100644 --- a/src/classes/Weapon.tsx +++ b/src/classes/Weapon.tsx @@ -6,6 +6,15 @@ import {ExpandMore} from "@mui/icons-material"; import {IMod} from "../types/Imod"; import {Irace} from "../types/Irace"; import WeaponFull from "./WeaponFull"; +import {styled} from '@mui/material/styles'; + +const StyledAccordion = styled(Accordion)(({ theme }) => ({ + 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' }, +})); interface IWeaponProps { weapon: IShortWeapon, @@ -34,23 +43,26 @@ class Weapon extends React.Component { const weapon = this.props.weapon return ( -
+
} + expandIcon={} aria-controls="panel1-content" id="panel1-header" + sx={{color: '#ffffff'}} > - {!this.props.isDefault && weapon.icon && !weapon.icon.endsWith("upgrade.png") ? + {!this.props.isDefault && weapon.icon && !weapon.icon.endsWith("upgrade.png") ? : ( weapon.isMeleeWeapon ? : - )} {weapon.name ? weapon.name : this.humanReadableName(weapon.filename)} + )} {weapon.name ? weapon.name : this.humanReadableName(weapon.filename)} + {this.props.isDefault && (default)} + - + -
+
); } } diff --git a/src/classes/WeaponFull.tsx b/src/classes/WeaponFull.tsx index c6fb4c3..3908605 100644 --- a/src/classes/WeaponFull.tsx +++ b/src/classes/WeaponFull.tsx @@ -22,6 +22,8 @@ import {Irace} from "../types/Irace"; import {IRaceUnits} from "../types/IUnitShort"; import {IRaceBuildings} from "../types/IBuildingShort"; import {ModifiersProvidesTable} from "./ModifiersProvideTable"; +import {DescriptionBox} from "../commons/DescriptionBox"; +import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; interface IWeaponFull { currentTable: string; @@ -37,16 +39,33 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, const StyledTableCell = styled(TableCell)(({theme}) => ({ [`&.${tableCellClasses.head}`]: { - backgroundColor: "rgb(244,244,244)", + backgroundColor: 'rgba(233, 69, 96, 0.15)', + color: '#ffffff', marginRight: 'auto', marginLeft: 'auto', - paddingLeft: 10 + paddingLeft: 10, + borderBottom: '2px solid rgba(233, 69, 96, 0.3)', }, [`&.${tableCellClasses.body}`]: { fontSize: 12, textAlign: 'center', + color: 'rgba(255, 255, 255, 0.85)', paddingRight: 18, - paddingLeft: 10 + paddingLeft: 10, + }, + })); + + const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.9)', + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, }, })); @@ -133,93 +152,76 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, return ( -
+
- -
- {props.isDefault ? "Default weapon" : weapon.description} -
-
+ {!props.isDefault && + + {weapon.description} + + } - + - {/*Damage*/} - Base damage - {weapon.minDamage} {weapon.maxDamage !== weapon.minDamage && "- " + weapon.maxDamage} - + Base damage + {weapon.minDamage} {weapon.maxDamage !== weapon.minDamage && "- " + weapon.maxDamage} - Accuracy - {weapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')} - + Accuracy + {weapon.accuracy.toFixed(3).replace(/[,.]?0+$/, '')} - Accuracy moving - {weapon.setupTime === 0 && weapon.accuracy - weapon.accuracyReductionMoving > 0 ? (weapon.accuracy - weapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"} - + Accuracy moving + {weapon.setupTime === 0 && weapon.accuracy - weapon.accuracyReductionMoving > 0 ? (weapon.accuracy - weapon.accuracyReductionMoving).toFixed(3).replace(/[,.]?0+$/, '') : "-"} -
- + - Reload time - {weapon.reloadTime} - + Reload time + {weapon.reloadTime} - Range - {weapon.maxRange ? (weapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""} - + Range + {weapon.maxRange ? (weapon.maxRange) : "-"} {weapon.minRange ? "(min " + (weapon.minRange) + ")" : ""} - Damage radius - {weapon.damageRadius ? (weapon.damageRadius) : "-"} - + Damage radius + {weapon.damageRadius ? (weapon.damageRadius) : "-"} -
- + - Cost + Cost {(weapon.costRequisition > 0 || weapon.costPower > 0) ? - + {weapon.costRequisition > 0 &&    @@ -228,24 +230,24 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean,    {weapon.costPower.toFixed(0)}} - : - + {(weapon.costTimeSeconds !== undefined && weapon.costTimeSeconds > 0) && +    + {weapon.costTimeSeconds}s} + : - } - Setup time - {weapon.setupTime != 0 ? (weapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"} - + Setup time + {weapon.setupTime != 0 ? (weapon.setupTime).toFixed(3).replace(/[,.]?0+$/, '') : "-"} - Throw force - {weapon.throwForceMin != 0 ? weapon.throwForceMin + " - " + weapon.throwForceMax : "-"} - + Throw force + {weapon.throwForceMin != 0 ? weapon.throwForceMin + " - " + weapon.throwForceMax : "-"}
@@ -259,9 +261,10 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, aria-label="Platform" value={weaoponFull.currentTable} onChange={handleChange} + sx={{ mb: 2 }} > - Dps - One hit average damage + Dps + One hit average damage { props.mod !== undefined && props.mod.technicalName === 'UltimateApocalypse' ? @@ -290,7 +293,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, - + {getTotalDamage(infLowPiercing)} {getTotalDamage(infMedPiercing)} {getTotalDamage(infHighPiercing)} @@ -325,13 +328,13 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, name={ArmorTypeNames.BuildingSuper}/> -
+
Morale
- + {getTotalDamage(vehLowPiercing)} {getTotalDamage(vehMedPiercing)} {getTotalDamage(vehHighPiercing)} @@ -379,13 +382,13 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, name={ArmorTypeNames.BuildingHigh}/> -
+
Morale
- + {getTotalDamage(infLowPiercing)} {getTotalDamage(infMedPiercing)} {getTotalDamage(infHighPiercing)} @@ -411,7 +414,7 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, {weapon.modifiers.length > 0 && -

Modifiers

+

Modifiers

} {weapon.requirements !== null && !props.isDefault && props.haveReinforceMenu && @@ -422,10 +425,10 @@ export default function WeaponFull(props: {weaponId: number, isDefault: Boolean, {renderAffectedResearches(weapon.affectedResearches, props.mod.id, props.race.id)} - {!props.isDefault && props.haveReinforceMenu && Hotkey: {weapon.hotkey}} + {!props.isDefault && props.haveReinforceMenu && Hotkey: {weapon.hotkey}}
) - } else return
loading...
; + } else return
loading...
; diff --git a/src/classes/WeaponSlot.tsx b/src/classes/WeaponSlot.tsx index 413c930..8256f37 100644 --- a/src/classes/WeaponSlot.tsx +++ b/src/classes/WeaponSlot.tsx @@ -4,6 +4,15 @@ import {IShortWeapon, IWeapon} from "../types/IUnit"; import {IMod} from "../types/Imod"; import {Irace} from "../types/Irace"; import WeaponShort from "./WeaponFull"; +import {Typography} from "@mui/material"; +import {styled} from '@mui/material/styles'; + +const SectionTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 700, + mb: 2, + color: '#ffffff', + fontSize: '1.25rem', +})); export interface WeaponSlotProps { hardpoint: number, @@ -33,7 +42,7 @@ const WeaponSlot= (props: WeaponSlotProps) => { return (
-

{header}

+ {header} {weaponsSorted.filter(wp => !wp[1].filename.includes("dummy")).map(wp =>
diff --git a/src/classes/building/BuildingAddon.tsx b/src/classes/building/BuildingAddon.tsx index 07072a3..34811d9 100644 --- a/src/classes/building/BuildingAddon.tsx +++ b/src/classes/building/BuildingAddon.tsx @@ -9,7 +9,8 @@ import { TableBody, TableCell, TableContainer, - TableRow + TableRow, + Typography } from "@mui/material"; import {IBuilding, IBuildingAddon} from "../../types/IBuilding"; import AvTimerOutlinedIcon from "@mui/icons-material/AvTimer"; @@ -17,6 +18,30 @@ import {ExpandMore} from "@mui/icons-material"; import {IMod} from "../../types/Imod"; import {getIcon, ModifiersProvidesTable,} from "../ModifiersProvideTable"; import Required from "../Required"; +import {styled} from '@mui/material/styles'; +import {DescriptionBox} from "../../commons/DescriptionBox"; + +const StyledAccordion = styled(Accordion)(({ theme }) => ({ + 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' }, +})); + +const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'rgba(255, 255, 255, 0.03)', + border: '1px solid rgba(255, 255, 255, 0.05)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.85)', + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, + }, +})); interface IBuildingAddonProps { addon: IBuildingAddon, @@ -31,27 +56,28 @@ function BuildingAddon(props: IBuildingAddonProps){ - return
+ return
} + expandIcon={} aria-controls="panel1-content" + sx={{color: '#ffffff'}} > - +   {addon.name} - + - + - Cost - + Cost + {addon.addonCostRequisition > 0 &&    @@ -83,9 +109,9 @@ function BuildingAddon(props: IBuildingAddonProps){
-
+ {addon.description} -
+
{addon.addonRequirement !== null && @@ -93,9 +119,9 @@ function BuildingAddon(props: IBuildingAddonProps){ } - Hotkey: {addon.hotkey} + Hotkey: {addon.hotkey} - + } diff --git a/src/classes/building/Research.tsx b/src/classes/building/Research.tsx index 8db663d..c31a8d6 100644 --- a/src/classes/building/Research.tsx +++ b/src/classes/building/Research.tsx @@ -20,6 +20,24 @@ import React from "react"; import {IBuilding} from "../../types/IBuilding"; import {IResearchShort} from "../../types/IResearchShort"; import ResearchFull from "./ResearchFull"; +import {styled} from '@mui/material/styles'; + +const StyledAccordion = styled(Accordion)(({ theme }) => ({ + 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' }, +})); + +const StyledLink = styled('a')(({ theme }) => ({ + color: '#e94560', + textDecoration: 'none', + '&:hover': { + color: '#ff6b6b', + textDecoration: 'underline', + }, +})); interface IResearchProps { @@ -33,20 +51,21 @@ function Research(props: IResearchProps){ const research = props.research const building = props.building - return
+ return
} + expandIcon={} aria-controls="panel1-content" + sx={{color: '#ffffff'}} > - +   {research.name} - + -
+
} @@ -55,12 +74,12 @@ export function renderAffectedResearches(researches: IResearchShort[], modId: nu function researchLink(rs: IResearchShort) { return -   +   {rs.name} - +
} - return
{researches.map(rs => + return
{researches.map(rs => rs.buildingId == null ? : Research affect: {researchLink(rs)}
)}
diff --git a/src/classes/building/ResearchFull.tsx b/src/classes/building/ResearchFull.tsx index 8173b3e..d4b8272 100644 --- a/src/classes/building/ResearchFull.tsx +++ b/src/classes/building/ResearchFull.tsx @@ -10,7 +10,8 @@ import { TableBody, TableCell, TableContainer, - TableRow + TableRow, + Typography } from "@mui/material"; import {ExpandMore} from "@mui/icons-material"; import {getIcon, ModifiersProvidesTable} from "../ModifiersProvideTable"; @@ -21,6 +22,22 @@ import {IBuilding} from "../../types/IBuilding"; import {IResearchShort} from "../../types/IResearchShort"; import {ResearchUrl, WeaponUrl} from "../../core/api"; import {IWeapon} from "../../types/IUnit"; +import {styled} from '@mui/material/styles'; +import {DescriptionBox} from "../../commons/DescriptionBox"; + +const StyledPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.9)', + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, + }, +})); interface IResearchFull { @@ -54,14 +71,14 @@ function ResearchFull(props: { id: number, building: IBuilding }){ - +
- Cost - + Cost + {research.costRequisition > 0 &&    @@ -93,9 +110,9 @@ function ResearchFull(props: { id: number, building: IBuilding }){
-
+ {research.description} -
+
@@ -107,9 +124,9 @@ function ResearchFull(props: { id: number, building: IBuilding }){ } - Hotkey: {research.hotkey} + Hotkey: {research.hotkey} - } else return
loading...
+ } else return
loading...
} export default ResearchFull; diff --git a/src/commons/DescriptionBox.tsx b/src/commons/DescriptionBox.tsx new file mode 100644 index 0000000..0ad4309 --- /dev/null +++ b/src/commons/DescriptionBox.tsx @@ -0,0 +1,12 @@ +import {styled} from '@mui/material/styles'; + +export const DescriptionBox = styled('div')(({ theme }) => ({ + color: 'rgba(255, 255, 255, 0.85)', + lineHeight: 1.7, + fontSize: '0.95rem', + padding: theme.spacing(3), + background: 'rgba(255, 255, 255, 0.03)', + borderRadius: '12px', + border: '1px solid rgba(255, 255, 255, 0.05)', + whiteSpace: 'pre-wrap', +})); \ No newline at end of file diff --git a/src/commons/StyledLink.tsx b/src/commons/StyledLink.tsx new file mode 100644 index 0000000..9b3f496 --- /dev/null +++ b/src/commons/StyledLink.tsx @@ -0,0 +1,12 @@ +import {Link} from "@mui/material"; +import {styled} from '@mui/material/styles'; + +export const StyledLink = styled(Link)(({ theme }) => ({ + color: '#e94560', + textDecoration: 'none', + transition: 'all 0.2s ease', + '&:hover': { + color: '#ff6b6b', + textDecoration: 'underline', + }, +})); diff --git a/src/index.css b/src/index.css index ec2585e..26ea0ef 100644 --- a/src/index.css +++ b/src/index.css @@ -1,13 +1,43 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); + body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + background-color: #0a0a0a; + color: #ffffff; } code { font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace; + background: rgba(255, 255, 255, 0.1); + padding: 2px 6px; + border-radius: 4px; +} + +* { + scrollbar-width: thin; + scrollbar-color: #e94560 #1a1a2e; +} + +*::-webkit-scrollbar { + width: 8px; +} + +*::-webkit-scrollbar-track { + background: #1a1a2e; +} + +*::-webkit-scrollbar-thumb { + background-color: #e94560; + border-radius: 4px; +} + +a { + color: inherit; + text-decoration: none; } diff --git a/src/pages/BuildingPage.tsx b/src/pages/BuildingPage.tsx index f47cf85..9630aec 100644 --- a/src/pages/BuildingPage.tsx +++ b/src/pages/BuildingPage.tsx @@ -5,6 +5,9 @@ import {IShortWeapon} from "../types/IUnit"; import '../css/Building.css' import { Button, + Box, + Container, + Divider, Grid2, Link, ListItem, @@ -12,11 +15,10 @@ import { Table, TableBody, TableCell, - TableContainer, TableRow, - Tooltip + Tooltip, + Typography, LinearProgress } from "@mui/material"; -import {ArrowBack} from "@mui/icons-material"; import ArmorType from "../classes/ArmorType"; import AvTimerOutlinedIcon from '@mui/icons-material/AvTimer'; import WeaponSlot from "../classes/WeaponSlot"; @@ -31,6 +33,43 @@ import Required from "../classes/Required"; import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable"; import Ability from "../classes/Ability"; import DeathExplosion from "../classes/DeathExplosion"; +import {styled} from '@mui/material/styles'; +import {DescriptionBox} from "../commons/DescriptionBox"; +import {ArrowBack} from "@mui/icons-material"; + +// Styled components +const BackButton = styled(Button)(({ theme }) => ({ + marginBottom: theme.spacing(3), + color: '#e94560', + borderColor: 'rgba(233, 69, 96, 0.5)', + textTransform: 'none', + fontWeight: 600, + '&:hover': { + borderColor: '#e94560', + backgroundColor: 'rgba(233, 69, 96, 0.1)', + }, +})); + +const SectionTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 700, + mb: 2, + color: '#ffffff', + fontSize: '1.25rem', +})); + +const StatsPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-root': { + color: 'rgba(255, 255, 255, 0.9)', + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, + }, +})); interface UintPageState { building: IBuilding, @@ -39,18 +78,30 @@ interface UintPageState { function Unit (unit: IUnitShort, modId: number, raceId: String) { - return ( - {unit.icon && } - {unit.name} - {unit.canDetect &&  } - ) + return ( + + + {unit.icon && } + {unit.name} + {unit.canDetect &&  } + + + ) } function Building(building: IBuilding, mod: IMod) { - document.title = building.name + document.title = building.name + " — " + mod.name let mapBuildingWeapons: Map> = new Map(); @@ -72,21 +123,36 @@ function Building(building: IBuilding, mod: IMod) { } return ( -
-

{mod.name} ({mod.version})

-

{building.icon && - } {buildingName}

+ + + + {building.icon && + } + {buildingName} + + + {mod.name} ({mod.version}) + + - + - +
- - Cost - + + Cost + {building.buildCostRequisition > 0 &&    @@ -114,7 +180,7 @@ function Building(building: IBuilding, mod: IMod) { {(building.requisitionIncome !== undefined && building.requisitionIncome > 0 || building.powerIncome !== undefined && building.powerIncome !== null || building.faithIncome !== undefined && building.faithIncome !== null) && - Resource income + Resource income {building.requisitionIncome !== undefined && building.requisitionIncome != null &&   } - - Armor type - + + Armor type + {building.armorType2 == null ? : } - - Health - + + Health +   {building.health} {building.healthRegeneration > 0 && +{building.healthRegeneration}/s} - - Sight - + + Sight + - - Repair max - - {building.repairMax} - + + Repair max + {building.repairMax}
-
+
- -
+ + {building.description} -
+
{building.requirements !== null && } {building.modifiers.length > 0 && - -

Affected on

+ + Affected on } {building.units.length > 0 && -

Unit production

+ Unit production {building.units.map(unit => Unit(unit, mod.id, building.race.id) @@ -194,7 +250,7 @@ function Building(building: IBuilding, mod: IMod) {
} {building.abilities.length > 0 && -

Abilities

+ Abilities {building.abilities.map(a => )} @@ -207,13 +263,13 @@ function Building(building: IBuilding, mod: IMod) {
} {building.addons.length > 0 && -

Addons

+ Addons {building.addons.map(b => )}
} {building.researches.length > 0 && -

Researches

+ Researches {building.researches.map(r => )} @@ -227,10 +283,12 @@ function Building(building: IBuilding, mod: IMod) { {renderAffectedResearches(building.affectedResearches, mod.id, building.race.id)}
- Hotkey: {building.hotkey} -
+ + Hotkey: {building.hotkey} + + -
) + ) } class BuildingPage extends React.Component { @@ -256,12 +314,24 @@ class BuildingPage extends React.Component { if (this.state != null && this.state.building != null && this.state.mod != null) { const backRef = "/mod/" + this.props.match.params.modId + "/race/" + this.props.match.params.raceId - return
- {Building(this.state.building, this.state.mod)} -
; + return ( + + } + href={backRef} + > + Back to race + + {Building(this.state.building, this.state.mod)} + + ); } else { - return "loading..."; + return ( + + + + ); } } } diff --git a/src/pages/ModPage.tsx b/src/pages/ModPage.tsx index a90ab8e..dda88bd 100644 --- a/src/pages/ModPage.tsx +++ b/src/pages/ModPage.tsx @@ -1,17 +1,58 @@ import {AvailableMods, AvailableRacesPart, AvailableUnits, IconUrl} from "../core/api"; -import React, {useEffect, useRef, useState} from "react"; -import {NavLink, useLocation, useParams} from "react-router-dom"; +import React, { useState } from "react"; import {withRouter} from "../core/withrouter"; import {IMod} from "../types/Imod"; -import {Irace} from "../types/Irace"; -import {Button, ListItem, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow} from "@mui/material"; -import {ArrowBack} from "@mui/icons-material"; -import {IRaceUnits, IUnitShort} from "../types/IUnitShort"; +import { + ArrowBack, +} from "@mui/icons-material"; +import { + Box, + Button, + Container, + Paper, + Typography, + Divider, + LinearProgress +} from "@mui/material"; +import {styled} from '@mui/material/styles'; import UnitsTable from "../classes/UnitsTable"; +// Styled components +const PageHeader = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(4, 4), + marginBottom: theme.spacing(4), + background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)', + borderRadius: '16px', + position: 'relative', + overflow: 'hidden', + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: 'radial-gradient(circle at 30% 50%, rgba(233, 69, 96, 0.1) 0%, transparent 50%)', + pointerEvents: 'none', + }, +})); + +const BackButton = styled(Button)(({ theme }) => ({ + marginBottom: theme.spacing(3), + color: '#e94560', + borderColor: 'rgba(233, 69, 96, 0.5)', + textTransform: 'none', + fontWeight: 600, + '&:hover': { + borderColor: '#e94560', + backgroundColor: 'rgba(233, 69, 96, 0.1)', + }, +})); + interface ModPageState { - mod: IMod, + mod: IMod | null, + loading: boolean, } @@ -20,31 +61,80 @@ class ModPage extends React.Component { constructor(props: any) { super(props); + this.state = { + mod: null, + loading: true + }; + fetch(AvailableMods + "/" + this.props.match.params.modId) .then(res => res.json()) .then((res: IMod) => { this.setState({ - mod : res + mod: res, + loading: false }); + }) + .catch(() => { + this.setState({ loading: false }); }); - - } render() { - if(this.state != null && this.state.mod != null ){ + if (this.state.loading) { + return ( + + + + ); + } - document.title = this.state.mod.name + if (this.state != null && this.state.mod != null) { + document.title = this.state.mod.name + " — ModWiki"; - return
- -

{this.state.mod.name} ({this.state.mod.version})

- -
; + return ( + + } + href="/" + > + Back to mods list + + + + + {this.state.mod.name} + + + Version {this.state.mod.version} + + + + + + + + + + ); } else { - return ""; + return ( + + + Mod not found + + + ); } } } diff --git a/src/pages/ModsPage.tsx b/src/pages/ModsPage.tsx index a211010..f6859f8 100644 --- a/src/pages/ModsPage.tsx +++ b/src/pages/ModsPage.tsx @@ -1,49 +1,272 @@ import {AvailableMods} from "../core/api"; -import React, {useEffect, useState} from "react"; -import {NavLink} from "react-router-dom"; -import {IMod} from "../types/Imod"; +import React from "react"; +import { NavLink } from "react-router-dom"; +import { IMod } from "../types/Imod"; +import { + Box, + Container, + Typography, + Grid, + Card, + CardContent, + CardActions, + Chip, + Button, + Divider, + useTheme, + Paper, + LinearProgress +} from "@mui/material"; +import { styled } from '@mui/material/styles'; -function Mods (mods: IMod[]) { +// Styled components +const PageHeader = styled(Paper)(({ theme }) => ({ + padding: theme.spacing(6, 4), + marginBottom: theme.spacing(6), + background: 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)', + borderRadius: '16px', + position: 'relative', + overflow: 'hidden', + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + background: 'radial-gradient(circle at 30% 50%, rgba(233, 69, 96, 0.1) 0%, transparent 50%)', + pointerEvents: 'none', + }, +})); - let mapWithModVersions: Map = new Map(); +const ModCard = styled(Card)(({ theme }) => ({ + height: '100%', + display: 'flex', + flexDirection: 'column', + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '16px', + transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', + position: 'relative', + overflow: 'hidden', + '&::before': { + content: '""', + position: 'absolute', + top: 0, + left: 0, + right: 0, + height: '3px', + background: 'linear-gradient(90deg, #e94560 0%, #ff6b6b 100%)', + transform: 'scaleX(0)', + transition: 'transform 0.3s ease', + }, + '&:hover': { + transform: 'translateY(-8px)', + boxShadow: '0 20px 40px rgba(233, 69, 96, 0.2)', + borderColor: 'rgba(233, 69, 96, 0.3)', + '&::before': { + transform: 'scaleX(1)', + }, + }, +})); - mods.forEach( mod => { - const versionList = mapWithModVersions.get(mod.name) - if(versionList == null){ - mapWithModVersions.set(mod.name, new Array(mod)) - } else { - versionList.push(mod) - } - }) +const VersionLink = styled(NavLink)(({ theme }) => ({ + display: 'flex', + alignItems: 'center', + padding: theme.spacing(1.5, 2), + marginBottom: theme.spacing(1), + background: 'rgba(255, 255, 255, 0.05)', + borderRadius: '10px', + textDecoration: 'none', + color: 'inherit', + transition: 'all 0.2s ease', + border: '1px solid rgba(255, 255, 255, 0.1)', + '&:hover': { + background: 'rgba(233, 69, 96, 0.15)', + borderColor: 'rgba(233, 69, 96, 0.3)', + transform: 'translateX(4px)', + }, +})); + +interface ModsProps { + mods: IMod[]; +} + +function Mods({ mods }: ModsProps) { + const theme = useTheme(); + + // Sort all mods by order, then group by name + const sortedMods = [...mods].sort((a, b) => a.order - b.order); + + const mapWithModVersions = new Map(); + sortedMods.forEach(mod => { + const versionList = mapWithModVersions.get(mod.name); + if (versionList == null) { + mapWithModVersions.set(mod.name, [mod]); + } else { + versionList.push(mod); + } + }); + + const getLatestVersion = (modName: String) => { + const sameMods = mapWithModVersions.get(modName) ?? []; + // The latest version is the one with the maximum id + const latest = sameMods.reduce((max, mod) => mod.id > max.id ? mod : max, sameMods[0]); + const nonBetaMods = sameMods.filter(m => !m.isBeta); + const betaMods = sameMods.filter(m => m.isBeta); + return { + latest: latest, + allVersions: sameMods, + hasBeta: betaMods.length > 0, + betaVersion: betaMods[0] + }; + }; + function ModCardComponent(modName: String, index: number) { + const sameMods = mapWithModVersions.get(modName) ?? []; + const { latest, hasBeta, betaVersion } = getLatestVersion(modName); - function Mod (modName: String) { - let sameMods = mapWithModVersions.get(modName) ?? [] + return ( + + + + + {modName} + + + + - let lastBeta = sameMods.filter((m) => m.isBeta).reverse()[0] + + + {hasBeta && ( + + )} + - return( -
-

{modName}

- {modName == "Unification new races" && } - {sameMods.filter((m) => !m.isBeta).map(mod => -
    {mod.version}
)} - {sameMods.find((m) => m.isBeta) != null &&
Beta versions:
} - {lastBeta != null && -
    {lastBeta.version}
- } -
- ) + + + + Main versions: + + + {sameMods.filter(m => !m.isBeta).map(mod => ( + + + + Version {mod.version} + + + → + + + + ))} + + {hasBeta && ( + + + Beta versions: + + {betaVersion && ( + + + + Version {betaVersion.version} (Beta) + + + → + + + + )} + + )} +
+ + + + +
+ ); } - return( - [...mapWithModVersions.keys()].map(m => Mod(m)) - ) + return ( + + + + + {[...new Set(sortedMods.map(m => m.name))].map((modName, index) => ( + + {ModCardComponent(modName, index)} + + ))} + + + + ); } interface ModsPageState { - mods: IMod[] + mods: IMod[]; + loading: boolean; } class ModsPage extends React.Component { @@ -51,22 +274,36 @@ class ModsPage extends React.Component { constructor({props}: { props: any }) { super(props); this.state = { - mods: [] - } + mods: [], + loading: true + }; } async componentDidMount() { - const response = await fetch(AvailableMods); - const data: IMod[] = await response.json(); - console.log(data); - this.setState({ - mods : data - }); + try { + const response = await fetch(AvailableMods); + const data: IMod[] = await response.json(); + this.setState({ + mods: data, + loading: false + }); + } catch (error) { + console.error('Error loading mods:', error); + this.setState({ loading: false }); + } } render() { - return
{Mods(this.state.mods)}
; + if (this.state.loading) { + return ( + + + + ); + } + + return ; } } -export default ModsPage \ No newline at end of file +export default ModsPage; diff --git a/src/pages/RacePageFast.tsx b/src/pages/RacePageFast.tsx index 26aa0ee..af0a034 100644 --- a/src/pages/RacePageFast.tsx +++ b/src/pages/RacePageFast.tsx @@ -8,29 +8,56 @@ import { Accordion, AccordionDetails, AccordionSummary, + Box, Button, - ButtonGroup, - Grid2, Link, + Container, + Divider, + Grid2, LinearProgress, + Link, List, ListItem, - ListSubheader, Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - ToggleButton, - ToggleButtonClassKey, - ToggleButtonGroup, - Typography + Typography, } from "@mui/material"; -import {ArrowBack, ArrowDropDown, ExpandMore} from "@mui/icons-material"; -import ArmorType from "../classes/ArmorType"; -import Weapon from "../classes/Weapon"; +import {ArrowBack, ExpandMore} from "@mui/icons-material"; import {IRaceUnits, IUnitShort} from "../types/IUnitShort"; import {IBuildingShort, IRaceBuildings} from "../types/IBuildingShort"; +import {styled} from '@mui/material/styles'; +import {StyledLink} from "../commons/StyledLink"; + +// Styled components +const BackButton = styled(Button)(({ theme }) => ({ + marginBottom: theme.spacing(3), + color: '#e94560', + borderColor: 'rgba(233, 69, 96, 0.5)', + textTransform: 'none', + fontWeight: 600, + '&:hover': { + borderColor: '#e94560', + backgroundColor: 'rgba(233, 69, 96, 0.1)', + }, +})); + +const SectionTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 700, + mb: 2, + color: '#ffffff', + fontSize: '1.25rem', +})); + +const UnitCard = styled(Paper)(({ theme }) => ({ + background: 'rgba(255, 255, 255, 0.03)', + border: '1px solid rgba(255, 255, 255, 0.05)', + borderRadius: '10px', + padding: theme.spacing(1.5, 2), + transition: 'all 0.2s ease', + '&:hover': { + background: 'rgba(233, 69, 96, 0.1)', + borderColor: 'rgba(233, 69, 96, 0.3)', + }, +})); + +const BuildingCard = styled(UnitCard)(({ theme }) => ({})); interface RacePageState { mod: IMod, @@ -41,13 +68,27 @@ interface RacePageState { function Unit(unit: IUnitShort, modId: number, raceId: String) { - return ( - {unit.icon && } - {unit.name} - {unit.canDetect &&  } - - ) + return ( + + {unit.icon && } + {unit.name} + {unit.canDetect &&  } + + ) } function UnitSmall(unit: IUnitShort, modId: number, raceId: String) { @@ -59,23 +100,33 @@ function UnitSmall(unit: IUnitShort, modId: number, raceId: String) { unitName = unit.name; } - return ( + return ( {unit.icon && } - {unitName} + {unitName} {unit.canDetect &&  }
) + src="/images/DETECT_YES.webp"/>}
) } function Building(building: IBuildingShort, modId: number, raceId: String) { - return ( - {building.icon && } - {building.name} - {building.canDetect &&  } - - - {building.units.map(unit => UnitSmall(unit, modId, raceId))} + return ( + + + + {building.icon && } + {building.name} + {building.canDetect &&  } + + {building.units.map(unit => UnitSmall(unit, modId, raceId))} + + ) } @@ -127,59 +178,61 @@ class Units extends React.Component { document.title = this.state.buildings?.race.name } + const accordionSx = { + 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' }, + }; + return (this.state.buildings != null ? -
+ {this.state.buildings.buildings.map(building => Building(building, this.props.modId, this.props.raceId))} - {this.state.buildings.buildingsAdvanced.length > 0 &&

- {this.state.buildings.buildingsAdvanced.map(building => Building(building, this.props.modId, this.props.raceId))} -
} - - - - } - aria-controls="units-accordion" - id="units-accordion" - > -

All units

-
- - - -

Infantry

- {this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId))} -
- -

Tech

- - {this.state.units.tech.map(unit => Unit(unit, this.props.modId, this.props.raceId))} - -
- -

Support

- - {this.state.units.support.map(unit => Unit(unit, this.props.modId, this.props.raceId))} - -
+ {this.state.buildings.buildingsAdvanced.length > 0 && + + Advanced buildings + + {this.state.buildings.buildingsAdvanced.map(building => Building(building, this.props.modId, this.props.raceId))} +
} + + + } + aria-controls="units-accordion" + id="units-accordion" + sx={{color: '#ffffff'}} + > + All units + + + + + Infantry + {this.state.units.infantry.map(unit => Unit(unit, this.props.modId, this.props.raceId))} - - -

-
-
: "Loading" + + Tech + + {this.state.units.tech.map(unit => Unit(unit, this.props.modId, this.props.raceId))} + + + + Support + + {this.state.units.support.map(unit => Unit(unit, this.props.modId, this.props.raceId))} + + +
+ +
+ + : Loading ) } else { - return "Loading..."; + return Loading...; } } } @@ -188,7 +241,6 @@ class RacePageFast extends React.Component { constructor(props: any) { super(props); - console.log(this.props.match.params.id); } @@ -214,13 +266,43 @@ class RacePageFast extends React.Component { if (this.state != null && this.state.mod != null && this.state.race != null) { const backRef = "/mod/" + this.state.mod.id - return
-

{this.state.mod.name} ({this.state.mod.version}) - {this.state.race.name}

- -
; + return ( + + } + href={backRef} + > + Back to mod + + + + + {this.state.race.name} + + + {this.state.mod.name} ({this.state.mod.version}) + + + + + + ); } else { - return "loading..."; + return ( + + + + ); } } } diff --git a/src/pages/UnitPage.tsx b/src/pages/UnitPage.tsx index c83c38d..21bb632 100644 --- a/src/pages/UnitPage.tsx +++ b/src/pages/UnitPage.tsx @@ -7,15 +7,18 @@ import { Accordion, AccordionDetails, AccordionSummary, + Box, Button, - Grid2, + Container, + Divider, + Grid2, LinearProgress, Paper, Table, TableBody, TableCell, - TableContainer, TableRow, - Tooltip + Tooltip, + Typography, } from "@mui/material"; import {ArrowBack, ExpandMore} from "@mui/icons-material"; import ArmorType from "../classes/ArmorType"; @@ -30,6 +33,43 @@ import {renderAffectedResearches} from "../classes/building/Research"; import {ModifiersProvidesTable} from "../classes/ModifiersProvideTable"; import Ability from "../classes/Ability"; import DeathExplosion from "../classes/DeathExplosion"; +import {styled} from '@mui/material/styles'; +import {DescriptionBox} from "../commons/DescriptionBox"; + +// Styled components +const BackButton = styled(Button)(({ theme }) => ({ + marginBottom: theme.spacing(3), + color: '#e94560', + borderColor: 'rgba(233, 69, 96, 0.5)', + textTransform: 'none', + fontWeight: 600, + '&:hover': { + borderColor: '#e94560', + backgroundColor: 'rgba(233, 69, 96, 0.1)', + }, +})); + +const SectionTitle = styled(Typography)(({ theme }) => ({ + fontWeight: 700, + mb: 2, + color: '#ffffff', + fontSize: '1.25rem', +})); + +const StatsPaper = styled(Paper)(({ theme }) => ({ + background: 'linear-gradient(145deg, #1a1a2e 0%, #16213e 100%)', + border: '1px solid rgba(255, 255, 255, 0.1)', + borderRadius: '12px', + overflow: 'hidden', + '& .MuiTableBody .MuiTableRow-root': { + '&:last-child td, &:last-child th': { border: 0 }, + }, + '& .MuiTableBody .MuiTableRow-root .MuiTableCell-head': { + color: '#e94560', + fontWeight: 600, + }, +})); + interface UintPageState { unit: IUnit, @@ -39,7 +79,7 @@ interface UintPageState { function Unit(unit: IUnit, mod: IMod) { - document.title = unit.name + document.title = unit.name + " — " + mod.name const morale = (unit.moraleMax !== null) ?  {unit.moraleMax} @@ -71,7 +111,7 @@ function Unit(unit: IUnit, mod: IMod) { const SergeantShort = (props: sergeantProps) => { return ( - + {props.icon && }   {props.name} {props.canDetect &&   -

{mod.name} ({mod.version})

-

{unit.icon && - } {unit.name}

+ + + + {unit.icon && + } + {unit.name} + + + {mod.name} ({mod.version}) + + - + - + - - - Cost - + + + Cost + {unit.buildCostRequisition > 0 &&    @@ -131,7 +186,7 @@ function Unit(unit: IUnit, mod: IMod) { {(unit?.reinforceTime !== 0 && unit.reinforceTime !== null && unit.squadMaxSize > 1) && - Reinforce cost + Reinforce cost {unit.reinforceCostRequisition && unit.reinforceCostRequisition > 0 ?   - Resource income + Resource income {unit.requisitionIncome !== undefined && unit.requisitionIncome > 0 &&   1 && - Squad size - - {unit.squadStartSize} / {unit.squadMaxSize} - + Squad size + {unit.squadStartSize} / {unit.squadMaxSize} } - - Armor type - + + Armor type + {unit.armorType2 == null ? : } - - Health - + + Health +   {unit.health} {unit.healthRegeneration > 0 && @@ -207,78 +256,54 @@ function Unit(unit: IUnit, mod: IMod) { {unit.armour !== undefined && unit.armour !== 0 && {unit.armour} } - - Move speed - - {unit.moveSpeed} - + + Move speed + {unit.moveSpeed} - - Morale - - {morale} - + + Morale + {morale} - - Mass - - {unit.mass} - + + Mass + {unit.mass} - - Vision - - - + + Vision + {unit.repairMax !== undefined && unit.repairMax !== null && - - Repair max - - {unit.repairMax} - + + Repair max + {unit.repairMax} } {unit.repairSpeed !== undefined && unit.repairSpeed !== null && unit.repairCostPercent !== null && - - Repair - - {unit.repairSpeed} hp/s; {unit.repairCostPercent}% cost - + + Repair + {unit.repairSpeed} hp/s; {unit.repairCostPercent}% cost } {unit.mobValue != null && - - Mob value - - {unit.mobValue} - + + Mob value + {unit.mobValue} } {unit.squadLimit !== undefined && unit.squadLimit !== null && - - Limit - - {unit.squadLimit} - + + Limit + {unit.squadLimit} }
-
+
- -
+ + {unit.description} -
+
{unit.requirements !== null && @@ -286,19 +311,21 @@ function Unit(unit: IUnit, mod: IMod) { } {unit.sergeants.map(s => - + } - aria-controls="panel1-content" - id="panel1-header" - > + sx={{color: '#ffffff'}}> 0}/> - + -
-
)}
@@ -306,17 +333,17 @@ function Unit(unit: IUnit, mod: IMod) { unit.moraleBrakeModifiers.find(m => m.reference.includes("speed_maximum_modifier") && m.value === 1.2) !== undefined && unit.moraleBrakeModifiers.length === 2) && -

Morale broken unusual behavior

- -
} + Morale broken unusual behavior + +
} {unit.modifiers.length > 0 && -

Affected on

+ Affected on
} {unit.abilities.length > 0 && -

Abilities

+ Abilities {unit.abilities.map(a => )} @@ -338,10 +365,14 @@ function Unit(unit: IUnit, mod: IMod) { {renderAffectedResearches(unit.affectedResearches, mod.id, unit.race.id)}
- Hotkey: {unit.hotkey} -
+ + + Hotkey: {unit.hotkey} + + -
) + + ) } @@ -371,12 +402,24 @@ class UnitPage extends React.Component { if (this.state != null && this.state.unit != null && this.state.mod != null) { const backRef = "/mod/" + this.props.match.params.modId + "/race/" + this.props.match.params.raceId - return
- {Unit(this.state.unit, this.state.mod)} -
; + return ( + + } + href={backRef} + > + Back to race + + {Unit(this.state.unit, this.state.mod)} + + ); } else { - return
loading...
; + return ( + + + + ); } } } diff --git a/src/types/Imod.tsx b/src/types/Imod.tsx index 6d4a914..4b3cade 100644 --- a/src/types/Imod.tsx +++ b/src/types/Imod.tsx @@ -1,6 +1,7 @@ export interface IMod { id: number; name: string; + order: number; version: string; technicalName: string; isBeta: boolean;