import {AvailableMods} from "../core/api"; 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'; // Styled components const PageHeader = styled(Paper)(({ theme }) => ({ padding: theme.spacing(6, 4), marginBottom: theme.spacing(6), background: theme.palette.mode === 'dark' ? 'linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)' : 'linear-gradient(135deg, #ffffff 0%, #f0f0f0 50%, #e0e0e0 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(255, 255, 255, 0.05) 0%, #dee2e6 50%)', pointerEvents: 'none', }, })); const ModCard = styled(Card)(({ theme }) => ({ height: '100%', display: 'flex', flexDirection: 'column', 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)', position: 'relative', overflow: 'hidden', '&::before': { content: '""', position: 'absolute', top: 0, left: 0, right: 0, height: '3px', background: theme.palette.mode === 'dark' ? 'linear-gradient(90deg, #dee2e6 0%, #cccccc 100%)' : 'linear-gradient(90deg, #000000 0%, #333333 100%)', transform: 'scaleX(0)', transition: 'transform 0.3s ease', }, '&: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)', '&::before': { transform: 'scaleX(1)', }, }, })); const VersionLink = styled(NavLink)(({ theme }) => ({ display: 'flex', alignItems: 'center', padding: theme.spacing(1.5, 2), marginBottom: theme.spacing(1), background: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.05)' : 'rgba(0, 0, 0, 0.05)', borderRadius: '10px', textDecoration: 'none', color: 'inherit', transition: 'all 0.2s ease', border: `1px solid ${theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'}`, '&:hover': { background: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)', borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)', }, })); const ModTitle = styled(Typography)(({ theme }) => ({ fontWeight: 700, mb: 1, background: theme.palette.mode === 'dark' ? 'linear-gradient(135deg, #dee2e6 0%, #e0e0e0 100%)' : 'none', WebkitBackgroundClip: theme.palette.mode === 'dark' ? 'text' : 'initial', WebkitTextFillColor: theme.palette.mode === 'dark' ? '#dee2e6' : 'initial', color: theme.palette.mode === 'dark' ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', })); const ModDescription = styled(Typography)(({ theme }) => ({ color: theme.palette.text.secondary, lineHeight: 1.6, })); const VersionsChip = styled(Chip)(({ theme }) => ({ backgroundColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.15)' : 'rgba(0, 0, 0, 0.1)', color: theme.palette.mode === 'dark' ? '#dee2e6' : 'rgba(0, 0, 0, 0.85)', fontWeight: 600, })); const StyledDivider = styled(Divider)(({ theme }) => ({ my: 2, borderColor: theme.palette.mode === 'dark' ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)', })); const MainVersionsLabel = styled(Typography)(({ theme }) => ({ color: theme.palette.text.primary, fontWeight: 600, mb: 1.5, })); const VersionText = styled(Typography)(({ theme }) => ({ fontWeight: 600, color: theme.palette.text.secondary, })); const VersionArrow = styled(Typography)(({ theme }) => ({ color: theme.palette.text.secondary, })); const BetaVersionsLabel = styled(Typography)(({ theme }) => ({ color: 'rgba(255, 193, 7, 0.9)', fontWeight: 600, mb: 1, })); const OpenLatestButton = styled(Button)(({ theme }) => { const isDark = theme.palette.mode === 'dark'; return { width: '100%', color: isDark ? '#FFD700' : '#1976d2', borderColor: isDark ? 'rgba(255, 215, 0, 0.5)' : 'rgba(25, 118, 210, 0.5)', fontWeight: 600, borderRadius: '10px', textTransform: 'none', fontSize: '1rem', py: 1.5, '&:hover': { borderColor: isDark ? '#FFD700' : '#1976d2', backgroundColor: isDark ? 'rgba(255, 215, 0, 0.1)' : 'rgba(25, 118, 210, 0.1)', boxShadow: isDark ? '0 8px 20px rgba(255, 215, 0, 0.2)' : '0 8px 20px rgba(25, 118, 210, 0.2)', }, }; }); 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 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); return ( {modName} {sameMods.filter(m => !m.isBeta).map(mod => ( Version {mod.version} ))} {hasBeta && ( Beta versions: {betaVersion && ( Version {betaVersion.version} (Beta) )} )} ); } return ( {[...new Set(sortedMods.map(m => m.name))].map((modName, index) => ( {ModCardComponent(modName, index)} ))} ); } interface ModsPageState { mods: IMod[]; loading: boolean; } class ModsPage extends React.Component { constructor({props}: { props: any }) { super(props); this.state = { mods: [], loading: true }; } async componentDidMount() { // Очистка кэша сайта при заходе на главную страницу (один раз за сессию) if (!sessionStorage.getItem('cache_cleared')) { sessionStorage.setItem('cache_cleared', '1'); try { if ('caches' in window) { const names = await caches.keys(); await Promise.all(names.map(name => caches.delete(name))); } if ('serviceWorker' in navigator) { const regs = await navigator.serviceWorker.getRegistrations(); await Promise.all(regs.map(r => r.unregister())); } } catch (e) { console.error('Cache clear failed:', e); } } try { const response = await fetch(AvailableMods); const data: IMod[] = await response.json(); this.setState({ mods: data.filter(mod => !mod.isHide), loading: false }); } catch (error) { console.error('Error loading mods:', error); this.setState({ loading: false }); } } render() { if (this.state.loading) { return ( ); } return ; } } export default ModsPage;