434 lines
15 KiB
TypeScript
434 lines
15 KiB
TypeScript
import {
|
||
Box,
|
||
Typography,
|
||
Button,
|
||
Snackbar,
|
||
Alert,
|
||
LinearProgress,
|
||
} from '@mui/material';
|
||
import { useEffect, useState } from 'react';
|
||
import { useNavigate, useParams } from 'react-router-dom';
|
||
import ServerStatus from '../components/ServerStatus/ServerStatus';
|
||
import PopaPopa from '../components/popa-popa';
|
||
import SettingsIcon from '@mui/icons-material/Settings';
|
||
import React from 'react';
|
||
import SettingsModal from '../components/Settings/SettingsModal';
|
||
|
||
declare global {
|
||
interface Window {
|
||
electron: {
|
||
ipcRenderer: {
|
||
invoke(channel: string, ...args: unknown[]): Promise<any>;
|
||
on(channel: string, func: (...args: unknown[]) => void): void;
|
||
removeAllListeners(channel: string): void;
|
||
};
|
||
};
|
||
}
|
||
}
|
||
|
||
// Определяем тип для props
|
||
interface LaunchPageProps {
|
||
onLaunchPage?: () => void;
|
||
launchOptions?: {
|
||
// Делаем опциональным
|
||
downloadUrl: string;
|
||
apiReleaseUrl: string;
|
||
versionFileName: string;
|
||
packName: string;
|
||
memory: number;
|
||
baseVersion: string;
|
||
serverIp: string;
|
||
fabricVersion: string;
|
||
};
|
||
}
|
||
|
||
const LaunchPage = ({
|
||
onLaunchPage,
|
||
launchOptions = {} as any,
|
||
}: LaunchPageProps) => {
|
||
const navigate = useNavigate();
|
||
const { versionId } = useParams();
|
||
const [versionConfig, setVersionConfig] = useState<any>(null);
|
||
|
||
// Начальное состояние должно быть пустым или с минимальными значениями
|
||
const [config, setConfig] = useState<{
|
||
memory: number;
|
||
preserveFiles: string[];
|
||
}>({
|
||
memory: 0,
|
||
preserveFiles: [],
|
||
});
|
||
const [isDownloading, setIsDownloading] = useState(false);
|
||
const [downloadProgress, setDownloadProgress] = useState(0);
|
||
const [buffer, setBuffer] = useState(10);
|
||
const [installStatus, setInstallStatus] = useState('');
|
||
const [notification, setNotification] = useState<{
|
||
open: boolean;
|
||
message: string;
|
||
severity: 'success' | 'error' | 'info';
|
||
}>({ open: false, message: '', severity: 'info' });
|
||
const [installStep, setInstallStep] = useState('');
|
||
const [installMessage, setInstallMessage] = useState('');
|
||
const [open, setOpen] = React.useState(false);
|
||
const handleOpen = () => setOpen(true);
|
||
const handleClose = () => setOpen(false);
|
||
|
||
useEffect(() => {
|
||
const savedConfig = localStorage.getItem('launcher_config');
|
||
if (!savedConfig || !JSON.parse(savedConfig).accessToken) {
|
||
navigate('/login');
|
||
}
|
||
|
||
const progressListener = (...args: unknown[]) => {
|
||
const progress = args[0] as number;
|
||
setDownloadProgress(progress);
|
||
setBuffer(Math.min(progress + 10, 100));
|
||
};
|
||
|
||
const statusListener = (...args: unknown[]) => {
|
||
const status = args[0] as { step: string; message: string };
|
||
setInstallStep(status.step);
|
||
setInstallMessage(status.message);
|
||
};
|
||
|
||
window.electron.ipcRenderer.on('download-progress', progressListener);
|
||
window.electron.ipcRenderer.on('installation-status', statusListener);
|
||
|
||
return () => {
|
||
// Удаляем только конкретных слушателей, а не всех
|
||
// Это безопаснее, чем removeAllListeners
|
||
const cleanup = window.electron.ipcRenderer.on;
|
||
if (typeof cleanup === 'function') {
|
||
cleanup('download-progress', progressListener);
|
||
cleanup('installation-status', statusListener);
|
||
}
|
||
// Удаляем использование removeAllListeners
|
||
};
|
||
}, [navigate]);
|
||
|
||
useEffect(() => {
|
||
const fetchVersionConfig = async () => {
|
||
if (!versionId) return;
|
||
|
||
try {
|
||
// Сначала проверяем, есть ли конфигурация в localStorage
|
||
const savedConfig = localStorage.getItem('selected_version_config');
|
||
if (savedConfig) {
|
||
const parsedConfig = JSON.parse(savedConfig);
|
||
setVersionConfig(parsedConfig);
|
||
|
||
// Устанавливаем значения памяти и preserveFiles из конфигурации
|
||
setConfig({
|
||
memory: parsedConfig.memory || 4096,
|
||
preserveFiles: parsedConfig.preserveFiles || [],
|
||
});
|
||
|
||
// Очищаем localStorage
|
||
localStorage.removeItem('selected_version_config');
|
||
return;
|
||
}
|
||
|
||
// Если нет в localStorage, запрашиваем с сервера
|
||
const result = await window.electron.ipcRenderer.invoke(
|
||
'get-version-config',
|
||
{ versionId },
|
||
);
|
||
|
||
if (result.success) {
|
||
setVersionConfig(result.config);
|
||
setConfig({
|
||
memory: result.config.memory || 4096,
|
||
preserveFiles: result.config.preserveFiles || [],
|
||
});
|
||
} else {
|
||
// Если не удалось получить конфигурацию, используем значения по умолчанию
|
||
const defaultConfig = {
|
||
downloadUrl: '',
|
||
apiReleaseUrl: '',
|
||
versionFileName: `${versionId}_version.txt`,
|
||
packName: versionId || 'Comfort',
|
||
memory: 4096,
|
||
baseVersion: '1.21.4',
|
||
serverIp: 'popa-popa.ru',
|
||
fabricVersion: '0.16.14',
|
||
preserveFiles: ['popa-launcher-config.json'],
|
||
};
|
||
setVersionConfig(defaultConfig);
|
||
setConfig({
|
||
memory: defaultConfig.memory,
|
||
preserveFiles: defaultConfig.preserveFiles || [],
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка при получении настроек версии:', error);
|
||
// Используем значения по умолчанию
|
||
const defaultConfig = {
|
||
downloadUrl: '',
|
||
apiReleaseUrl: '',
|
||
versionFileName: `${versionId}_version.txt`,
|
||
packName: versionId || 'Comfort',
|
||
memory: 4096,
|
||
baseVersion: '1.21.4',
|
||
serverIp: 'popa-popa.ru',
|
||
fabricVersion: '0.16.14',
|
||
preserveFiles: ['popa-launcher-config.json'],
|
||
};
|
||
setVersionConfig(defaultConfig);
|
||
setConfig({
|
||
memory: defaultConfig.memory,
|
||
preserveFiles: defaultConfig.preserveFiles || [],
|
||
});
|
||
}
|
||
};
|
||
|
||
fetchVersionConfig();
|
||
}, [versionId]);
|
||
|
||
const showNotification = (
|
||
message: string,
|
||
severity: 'success' | 'error' | 'info',
|
||
) => {
|
||
setNotification({ open: true, message, severity });
|
||
};
|
||
|
||
const handleCloseNotification = () => {
|
||
setNotification({ ...notification, open: false });
|
||
};
|
||
|
||
// Функция для запуска игры с настройками выбранной версии
|
||
const handleLaunchMinecraft = async () => {
|
||
try {
|
||
setIsDownloading(true);
|
||
setDownloadProgress(0);
|
||
setBuffer(10);
|
||
|
||
// Используем настройки выбранной версии или дефолтные
|
||
const currentConfig = versionConfig || {
|
||
packName: versionId || 'Comfort',
|
||
memory: 4096,
|
||
baseVersion: '1.21.4',
|
||
serverIp: 'popa-popa.ru',
|
||
fabricVersion: '0.16.14',
|
||
preserveFiles: [],
|
||
};
|
||
|
||
// Проверяем, является ли это ванильной версией
|
||
const isVanillaVersion =
|
||
!currentConfig.downloadUrl || currentConfig.downloadUrl === '';
|
||
|
||
if (!isVanillaVersion) {
|
||
// Если это не ванильная версия, выполняем загрузку и распаковку
|
||
const packOptions = {
|
||
downloadUrl: currentConfig.downloadUrl,
|
||
apiReleaseUrl: currentConfig.apiReleaseUrl,
|
||
versionFileName: currentConfig.versionFileName,
|
||
packName: versionId || currentConfig.packName,
|
||
preserveFiles: config.preserveFiles,
|
||
};
|
||
|
||
// Передаем опции для скачивания
|
||
const downloadResult = await window.electron.ipcRenderer.invoke(
|
||
'download-and-extract',
|
||
packOptions,
|
||
);
|
||
|
||
if (downloadResult?.success) {
|
||
if (downloadResult.updated) {
|
||
showNotification(
|
||
`Сборка ${downloadResult.packName} успешно обновлена до версии ${downloadResult.version}`,
|
||
'success',
|
||
);
|
||
} else {
|
||
showNotification(
|
||
`Установлена актуальная версия сборки ${downloadResult.packName} (${downloadResult.version})`,
|
||
'info',
|
||
);
|
||
}
|
||
}
|
||
} else {
|
||
showNotification('Запускаем ванильный Minecraft...', 'info');
|
||
}
|
||
|
||
// Опции для запуска Minecraft
|
||
const savedConfig = JSON.parse(
|
||
localStorage.getItem('launcher_config') || '{}',
|
||
);
|
||
|
||
const options = {
|
||
accessToken: savedConfig.accessToken,
|
||
uuid: savedConfig.uuid,
|
||
username: savedConfig.username,
|
||
memory: config.memory,
|
||
baseVersion: currentConfig.baseVersion,
|
||
packName: versionId || currentConfig.packName,
|
||
serverIp: currentConfig.serverIp,
|
||
fabricVersion: currentConfig.fabricVersion,
|
||
// Для ванильной версии устанавливаем флаг
|
||
isVanillaVersion: isVanillaVersion,
|
||
versionToLaunchOverride: isVanillaVersion ? versionId : undefined,
|
||
};
|
||
|
||
const launchResult = await window.electron.ipcRenderer.invoke(
|
||
'launch-minecraft',
|
||
options,
|
||
);
|
||
|
||
if (launchResult?.success) {
|
||
showNotification('Minecraft успешно запущен!', 'success');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error:', error);
|
||
showNotification(`Ошибка: ${error.message}`, 'error');
|
||
} finally {
|
||
setIsDownloading(false);
|
||
}
|
||
};
|
||
|
||
// Функция для сохранения настроек
|
||
const savePackConfig = async () => {
|
||
try {
|
||
const configToSave = {
|
||
memory: config.memory,
|
||
preserveFiles: config.preserveFiles || [],
|
||
};
|
||
|
||
await window.electron.ipcRenderer.invoke('save-pack-config', {
|
||
packName: versionId || versionConfig?.packName || 'Comfort',
|
||
config: configToSave,
|
||
});
|
||
|
||
// Обновляем launchOptions
|
||
launchOptions.memory = config.memory;
|
||
|
||
showNotification('Настройки сохранены', 'success');
|
||
} catch (error) {
|
||
console.error('Ошибка при сохранении настроек:', error);
|
||
showNotification('Ошибка сохранения настроек', 'error');
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Box sx={{ gap: '1vh', display: 'flex', flexDirection: 'column' }}>
|
||
<PopaPopa />
|
||
|
||
<Typography variant="h4">Игровой сервер</Typography>
|
||
|
||
<Typography variant="h4">долбаёбов в Minecraft</Typography>
|
||
|
||
<Box>
|
||
<Typography variant="body1" sx={{ color: '#FFFFFF61' }}>
|
||
СЕРВЕР ГДЕ ВСЕМ НА ВАС ПОХУЙ
|
||
</Typography>
|
||
<Typography variant="body1" sx={{ color: '#FFFFFF61' }}>
|
||
СЕРВЕР ГДЕ РАЗРЕШЕНЫ ОДНОПОЛЫЕ БРАКИ
|
||
</Typography>
|
||
<Typography variant="body1" sx={{ color: '#FFFFFF61' }}>
|
||
СЕРВЕР ГДЕ ВСЕ ДОЛБАЕБЫ
|
||
</Typography>
|
||
<Typography variant="body1" sx={{ color: '#FFFFFF61' }}>
|
||
СЕРВЕР ГДЕ НА СПАВНЕ БУДЕТ ХУЙ (ВОЗМОЖНО)
|
||
</Typography>
|
||
<Typography variant="body1" sx={{ color: '#FFFFFF61' }}>
|
||
СЕРВЕР ЗА КОТОРЫЙ ВЫ ПРОДАДИТЕ МАТЬ
|
||
</Typography>
|
||
<Typography variant="body1" sx={{ color: '#FFFFFF61' }}>
|
||
ТЫ МОЖЕШЬ КУПИТЬ АДМИНКУ И ПОЛУЧИТЬ ПИЗДЫ
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Box>
|
||
<ServerStatus
|
||
serverIp={versionConfig?.serverIp || 'popa-popa.ru'}
|
||
refreshInterval={30000}
|
||
/>
|
||
</Box>
|
||
|
||
{isDownloading ? (
|
||
<Box sx={{ mb: 3, width: '100%' }}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||
<Box sx={{ width: '100%', mr: 1 }}>
|
||
<LinearProgress
|
||
variant="buffer"
|
||
value={downloadProgress}
|
||
valueBuffer={buffer}
|
||
/>
|
||
</Box>
|
||
<Box sx={{ minWidth: 35 }}>
|
||
<Typography
|
||
variant="body2"
|
||
sx={{ color: 'white' }}
|
||
>{`${Math.round(downloadProgress)}%`}</Typography>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
) : (
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
gap: '1vw',
|
||
width: '100%', // родитель занимает всю ширину
|
||
}}
|
||
>
|
||
{/* Первая кнопка — растягивается на всё доступное пространство */}
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
onClick={handleLaunchMinecraft}
|
||
sx={{
|
||
flexGrow: 1, // занимает всё свободное место
|
||
width: 'auto', // ширина подстраивается
|
||
borderRadius: '3vw',
|
||
fontFamily: 'Benzin-Bold',
|
||
background: 'linear-gradient(90deg, #3B96FF 0%, #FFB7ED 100%)',
|
||
}}
|
||
>
|
||
Запустить Minecraft
|
||
</Button>
|
||
|
||
{/* Вторая кнопка — квадратная, фиксированного размера (ширина = высоте) */}
|
||
<Button
|
||
variant="contained"
|
||
sx={{
|
||
flexShrink: 0, // не сжимается
|
||
aspectRatio: '1', // ширина = высоте
|
||
backgroundColor: 'grey',
|
||
borderRadius: '3vw',
|
||
minHeight: 'unset',
|
||
minWidth: 'unset',
|
||
height: '100%', // занимает полную высоту родителя
|
||
}}
|
||
onClick={handleOpen}
|
||
>
|
||
<SettingsIcon />
|
||
</Button>
|
||
</Box>
|
||
)}
|
||
|
||
<Snackbar
|
||
open={notification.open}
|
||
autoHideDuration={6000}
|
||
onClose={handleCloseNotification}
|
||
>
|
||
<Alert
|
||
onClose={handleCloseNotification}
|
||
severity={notification.severity}
|
||
sx={{ width: '100%' }}
|
||
>
|
||
{notification.message}
|
||
</Alert>
|
||
</Snackbar>
|
||
|
||
<SettingsModal
|
||
open={open}
|
||
onClose={handleClose}
|
||
config={config}
|
||
onConfigChange={setConfig}
|
||
packName={versionId || versionConfig?.packName || 'Comfort'}
|
||
onSave={savePackConfig}
|
||
/>
|
||
</Box>
|
||
);
|
||
};
|
||
|
||
export default LaunchPage;
|