feat: improve Minecraft version handling

This commit is contained in:
2025-07-13 23:37:46 +05:00
parent 815ce286f7
commit 942066ea76
9 changed files with 491 additions and 217 deletions

View File

@ -5,7 +5,6 @@ import {
Snackbar,
Alert,
LinearProgress,
Modal,
} from '@mui/material';
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
@ -13,8 +12,7 @@ import ServerStatus from '../components/ServerStatus/ServerStatus';
import PopaPopa from '../components/popa-popa';
import SettingsIcon from '@mui/icons-material/Settings';
import React from 'react';
import MemorySlider from '../components/Login/MemorySlider';
import FilesSelector from '../components/FilesSelector';
import SettingsModal from '../components/Settings/SettingsModal';
declare global {
interface Window {
@ -44,7 +42,10 @@ interface LaunchPageProps {
};
}
const LaunchPage = ({ onLaunchPage, launchOptions = {} as any }: LaunchPageProps) => {
const LaunchPage = ({
onLaunchPage,
launchOptions = {} as any,
}: LaunchPageProps) => {
const navigate = useNavigate();
const { versionId } = useParams();
const [versionConfig, setVersionConfig] = useState<any>(null);
@ -417,65 +418,14 @@ const LaunchPage = ({ onLaunchPage, launchOptions = {} as any }: LaunchPageProps
</Alert>
</Snackbar>
<Modal
<SettingsModal
open={open}
onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
background:
'linear-gradient(-242.94deg, #000000 39.07%, #3b4187 184.73%)',
border: '2px solid #000',
boxShadow: 24,
p: 4,
borderRadius: '3vw',
gap: '1vh',
display: 'flex',
flexDirection: 'column',
}}
>
<Typography id="modal-modal-title" variant="body1" component="h2">
Файлы и папки, которые будут сохранены после переустановки сборки
</Typography>
<FilesSelector
packName={versionId || versionConfig?.packName || 'Comfort'}
initialSelected={config.preserveFiles}
onSelectionChange={(selected) => {
setConfig((prev) => ({ ...prev, preserveFiles: selected }));
}}
/>
<Typography variant="body1" sx={{ color: 'white' }}>
Оперативная память выделенная для Minecraft
</Typography>
<MemorySlider
memory={config.memory}
onChange={(e, value) => {
setConfig((prev) => ({ ...prev, memory: value as number }));
}}
/>
<Button
variant="contained"
color="success"
onClick={() => {
savePackConfig();
handleClose();
}}
sx={{
borderRadius: '3vw',
fontFamily: 'Benzin-Bold',
}}
>
Сохранить
</Button>
</Box>
</Modal>
config={config}
onConfigChange={setConfig}
packName={versionId || versionConfig?.packName || 'Comfort'}
onSave={savePackConfig}
/>
</Box>
);
};

View File

@ -38,13 +38,25 @@ const Login = () => {
console.log(
'Не удалось обновить токен, требуется новая авторизация',
);
const newSession = await authenticateWithElyBy(
config.username,
config.password,
saveConfig,
);
if (!newSession) {
console.log('Авторизация не удалась');
// Очищаем недействительные токены
saveConfig({
accessToken: '',
clientToken: '',
});
// Пытаемся выполнить новую авторизацию
if (config.password) {
const newSession = await authenticateWithElyBy(
config.username,
config.password,
saveConfig,
);
if (!newSession) {
console.log('Авторизация не удалась');
return;
}
} else {
console.log('Требуется ввод пароля для новой авторизации');
return;
}
}
@ -53,6 +65,13 @@ const Login = () => {
}
} else {
console.log('Токен отсутствует, выполняем авторизацию...');
// Проверяем наличие пароля
if (!config.password) {
console.log('Ошибка: не указан пароль');
alert('Введите пароль!');
return;
}
const session = await authenticateWithElyBy(
config.username,
config.password,
@ -68,6 +87,11 @@ const Login = () => {
navigate('/');
} catch (error) {
console.log(`ОШИБКА при авторизации: ${error.message}`);
// Очищаем недействительные токены при ошибке
saveConfig({
accessToken: '',
clientToken: '',
});
}
};

View File

@ -9,8 +9,15 @@ import {
CardActions,
Button,
CircularProgress,
Modal,
List,
ListItem,
ListItemText,
IconButton,
} from '@mui/material';
import { useNavigate } from 'react-router-dom';
import AddIcon from '@mui/icons-material/Add';
import DownloadIcon from '@mui/icons-material/Download';
interface VersionCardProps {
id: string;
@ -32,51 +39,30 @@ const VersionCard: React.FC<VersionCardProps> = ({
sx={{
backgroundColor: 'rgba(30, 30, 50, 0.8)',
backdropFilter: 'blur(10px)',
width: '100%',
height: '100%',
width: '35vw',
height: '35vh',
minWidth: 'unset',
minHeight: 'unset',
display: 'flex',
flexDirection: 'column',
borderRadius: '16px',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
transition: 'transform 0.3s, box-shadow 0.3s',
overflow: 'hidden',
'&:hover': {
transform: 'translateY(-8px)',
boxShadow: '0 12px 40px rgba(0, 0, 0, 0.5)',
},
cursor: 'pointer',
}}
onClick={() => onSelect(id)}
>
<Box sx={{ position: 'relative', overflow: 'hidden', width: '100%' }}>
<CardMedia
component="img"
height="180"
image={'https://placehold.co/300x140?text=' + name}
alt={name}
sx={{
transition: 'transform 0.5s',
'&:hover': {
transform: 'scale(1.05)',
},
}}
/>
<Box
sx={{
position: 'absolute',
top: 0,
right: 0,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
color: '#fff',
padding: '4px 12px',
borderBottomLeftRadius: '12px',
}}
>
<Typography variant="caption" fontWeight="bold">
{version}
</Typography>
</Box>
</Box>
<CardContent sx={{ flexGrow: 1, padding: '16px' }}>
<CardContent
sx={{
flexGrow: 1,
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '10%',
}}
>
<Typography
gutterBottom
variant="h5"
@ -89,37 +75,7 @@ const VersionCard: React.FC<VersionCardProps> = ({
>
{name}
</Typography>
<Typography
variant="body2"
sx={{
color: 'rgba(255, 255, 255, 0.7)',
marginBottom: '12px',
}}
>
Версия: {version}
</Typography>
</CardContent>
<CardActions sx={{ padding: '0 16px 16px' }}>
<Button
fullWidth
variant="contained"
color="primary"
onClick={() => onSelect(id)}
sx={{
borderRadius: '12px',
textTransform: 'none',
fontWeight: 'bold',
padding: '10px 0',
background: 'linear-gradient(90deg, #3a7bd5, #6d5bf1)',
'&:hover': {
background: 'linear-gradient(90deg, #4a8be5, #7d6bf1)',
},
}}
>
Играть
</Button>
</CardActions>
</Card>
);
};
@ -142,40 +98,62 @@ interface VersionInfo {
};
}
interface AvailableVersionInfo {
id: string;
name: string;
version: string;
imageUrl?: string;
config: {
downloadUrl: string;
apiReleaseUrl: string;
versionFileName: string;
packName: string;
memory: number;
baseVersion: string;
serverIp: string;
fabricVersion: string;
preserveFiles: string[];
};
}
// В компоненте VersionsExplorer
export const VersionsExplorer = () => {
const [versions, setVersions] = useState<VersionInfo[]>([]);
const [installedVersions, setInstalledVersions] = useState<VersionInfo[]>([]);
const [availableVersions, setAvailableVersions] = useState<
AvailableVersionInfo[]
>([]);
const [loading, setLoading] = useState(true);
const [modalOpen, setModalOpen] = useState(false);
const [downloadLoading, setDownloadLoading] = useState<string | null>(null);
const navigate = useNavigate();
useEffect(() => {
const fetchVersions = async () => {
try {
const result = await window.electron.ipcRenderer.invoke(
'get-available-versions',
setLoading(true);
// Получаем список установленных версий через IPC
const installedResult = await window.electron.ipcRenderer.invoke(
'get-installed-versions',
);
if (result.success) {
// Для каждой версии получаем её конфигурацию
const versionsWithConfig = await Promise.all(
result.versions.map(async (version: VersionInfo) => {
const configResult = await window.electron.ipcRenderer.invoke(
'get-version-config',
{ versionId: version.id },
);
if (installedResult.success) {
setInstalledVersions(installedResult.versions);
}
return {
...version,
config: configResult.success ? configResult.config : undefined,
};
}),
);
setVersions(versionsWithConfig);
} else {
console.error('Ошибка получения версий:', result.error);
// Получаем доступные версии с GitHub Gist
const availableResult = await window.electron.ipcRenderer.invoke(
'get-available-versions',
{
gistUrl:
'https://gist.githubusercontent.com/DIKER0K/06cd12fb3a4d08b1f0f8c763a7d05e06/raw/versions.json',
},
);
if (availableResult.success) {
setAvailableVersions(availableResult.versions);
}
} catch (error) {
console.error('Ошибка при запросе версий:', error);
console.error('Ошибка при загрузке версий:', error);
// Можно добавить обработку ошибки, например показать уведомление
} finally {
setLoading(false);
}
@ -185,7 +163,6 @@ export const VersionsExplorer = () => {
}, []);
const handleSelectVersion = (version: VersionInfo) => {
// Сохраняем конфигурацию в localStorage для использования в LaunchPage
localStorage.setItem(
'selected_version_config',
JSON.stringify(version.config || {}),
@ -193,45 +170,94 @@ export const VersionsExplorer = () => {
navigate(`/launch/${version.id}`);
};
// Тестовая версия, если нет доступных
const displayVersions =
versions.length > 0
? versions
: [
{
id: 'Comfort',
name: 'Comfort',
version: '1.21.4-fabric0.16.14',
imageUrl: 'https://via.placeholder.com/300x140?text=Comfort',
config: {
downloadUrl:
'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip',
apiReleaseUrl:
'https://api.github.com/repos/DIKER0K/Comfort/releases/latest',
versionFileName: 'comfort_version.txt',
packName: 'Comfort',
memory: 4096,
baseVersion: '1.21.4',
serverIp: 'popa-popa.ru',
fabricVersion: '0.16.14',
preserveFiles: ['popa-launcher-config.json'],
},
},
];
const handleAddVersion = () => {
setModalOpen(true);
};
const handleCloseModal = () => {
setModalOpen(false);
};
const handleDownloadVersion = async (version: AvailableVersionInfo) => {
try {
setDownloadLoading(version.id);
// Скачивание и установка выбранной версии
const downloadResult = await window.electron.ipcRenderer.invoke(
'download-and-extract',
{
downloadUrl: version.config.downloadUrl,
apiReleaseUrl: version.config.apiReleaseUrl,
versionFileName: version.config.versionFileName,
packName: version.id,
preserveFiles: version.config.preserveFiles || [],
},
);
if (downloadResult?.success) {
// Добавляем скачанную версию в список установленных
setInstalledVersions((prev) => [...prev, version]);
setModalOpen(false);
}
} catch (error) {
console.error(`Ошибка при скачивании версии ${version.id}:`, error);
} finally {
setDownloadLoading(null);
}
};
// Карточка добавления новой версии
const AddVersionCard = () => (
<Card
sx={{
backgroundColor: 'rgba(30, 30, 50, 0.8)',
backdropFilter: 'blur(10px)',
width: '35vw',
height: '35vh',
minWidth: 'unset',
minHeight: 'unset',
display: 'flex',
flexDirection: 'column',
borderRadius: '16px',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
transition: 'transform 0.3s, box-shadow 0.3s',
overflow: 'hidden',
justifyContent: 'center',
alignItems: 'center',
cursor: 'pointer',
}}
onClick={handleAddVersion}
>
<AddIcon sx={{ fontSize: 60, color: '#fff' }} />
<Typography
variant="h6"
sx={{
color: '#fff',
}}
>
Добавить
</Typography>
<Typography
variant="h6"
sx={{
color: '#fff',
}}
>
версию
</Typography>
</Card>
);
return (
<Box
sx={{
width: '100vw',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
paddingLeft: '5vw',
paddingRight: '5vw',
}}
>
<Typography variant="h4" sx={{ mb: 4 }}>
Доступные версии
</Typography>
{loading ? (
<Box display="flex" justifyContent="center" my={5}>
<CircularProgress />
@ -241,28 +267,128 @@ export const VersionsExplorer = () => {
container
spacing={3}
sx={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
width: '100%',
overflowY: 'auto',
justifyContent: 'center',
}}
>
{displayVersions.map((version) => (
<Grid key={version.id} size={{ xs: 12, sm: 6, md: 4 }}>
<VersionCard
id={version.id}
name={version.name}
imageUrl={
version.imageUrl ||
'https://via.placeholder.com/300x140?text=Minecraft'
}
version={version.version}
onSelect={() => handleSelectVersion(version)}
/>
{/* Показываем установленные версии или дефолтную, если она есть */}
{installedVersions.length > 0 ? (
installedVersions.map((version) => (
<Grid
key={version.id}
size={{ xs: 'auto', sm: 'auto', md: 'auto' }}
>
<VersionCard
id={version.id}
name={version.name}
imageUrl={
version.imageUrl ||
'https://via.placeholder.com/300x140?text=Minecraft'
}
version={version.version}
onSelect={() => handleSelectVersion(version)}
/>
</Grid>
))
) : (
// Если нет ни одной версии, показываем карточку добавления
<Grid size={{ xs: 'auto', sm: 'auto', md: 'auto' }}>
<AddVersionCard />
</Grid>
))}
)}
{/* Всегда добавляем карточку для добавления новых версий */}
{installedVersions.length > 0 && (
<Grid size={{ xs: 'auto', sm: 'auto', md: 'auto' }}>
<AddVersionCard />
</Grid>
)}
</Grid>
)}
{/* Модальное окно для выбора версии для скачивания */}
<Modal
open={modalOpen}
onClose={handleCloseModal}
aria-labelledby="modal-versions"
aria-describedby="modal-available-versions"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 400,
maxHeight: '80vh',
overflowY: 'auto',
background: 'linear-gradient(45deg, #000000 10%, #3b4187 184.73%)',
border: '2px solid #000',
boxShadow: 24,
p: 4,
borderRadius: '3vw',
gap: '1vh',
display: 'flex',
flexDirection: 'column',
backdropFilter: 'blur(10px)',
}}
>
<Typography variant="h6" component="h2" sx={{ color: '#fff' }}>
Доступные версии для скачивания
</Typography>
{availableVersions.length === 0 ? (
<Typography sx={{ color: '#fff', mt: 2 }}>
Загрузка доступных версий...
</Typography>
) : (
<List sx={{ mt: 2 }}>
{availableVersions.map((version) => (
<ListItem
key={version.id}
sx={{
borderRadius: '8px',
mb: 1,
backgroundColor: 'rgba(255, 255, 255, 0.1)',
cursor: 'pointer',
'&:hover': {
backgroundColor: 'rgba(255, 255, 255, 0.2)',
},
}}
onClick={() => handleSelectVersion(version)}
>
<ListItemText
primary={version.name}
secondary={version.version}
primaryTypographyProps={{ color: '#fff' }}
secondaryTypographyProps={{
color: 'rgba(255,255,255,0.7)',
}}
/>
</ListItem>
))}
</List>
)}
<Button
onClick={handleCloseModal}
variant="outlined"
sx={{
mt: 3,
alignSelf: 'center',
borderColor: '#fff',
color: '#fff',
'&:hover': {
borderColor: '#ccc',
backgroundColor: 'rgba(255,255,255,0.1)',
},
}}
>
Закрыть
</Button>
</Box>
</Modal>
</Box>
);
};