Compare commits
2 Commits
31a26dc1ce
...
942066ea76
Author | SHA1 | Date | |
---|---|---|---|
942066ea76 | |||
815ce286f7 |
@ -119,7 +119,7 @@ const createWindow = async () => {
|
|||||||
width: 1024,
|
width: 1024,
|
||||||
height: 850,
|
height: 850,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
resizable: false,
|
resizable: true,
|
||||||
frame: false,
|
frame: false,
|
||||||
icon: getAssetPath('icon.png'),
|
icon: getAssetPath('icon.png'),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
|
@ -804,6 +804,106 @@ export function initMinecraftHandlers() {
|
|||||||
return { success: false, error: error.message };
|
return { success: false, error: error.message };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ПРОБЛЕМА: У вас два обработчика для одного и того же канала 'get-installed-versions'
|
||||||
|
// РЕШЕНИЕ: Объединим логику в один обработчик, а из второго обработчика вызовем функцию getInstalledVersions
|
||||||
|
|
||||||
|
// Сначала создаем общую функцию для получения установленных версий
|
||||||
|
function getInstalledVersions() {
|
||||||
|
try {
|
||||||
|
const appPath = path.dirname(app.getPath('exe'));
|
||||||
|
const minecraftDir = path.join(appPath, '.minecraft');
|
||||||
|
const versionsDir = path.join(minecraftDir, 'versions');
|
||||||
|
|
||||||
|
if (!fs.existsSync(versionsDir)) {
|
||||||
|
return { success: true, versions: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = fs.readdirSync(versionsDir);
|
||||||
|
const versions = [];
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
const versionPath = path.join(versionsDir, item);
|
||||||
|
if (fs.statSync(versionPath).isDirectory()) {
|
||||||
|
// Проверяем, есть ли конфигурация для пакета
|
||||||
|
const versionJsonPath = path.join(versionPath, `${item}.json`);
|
||||||
|
let versionInfo = {
|
||||||
|
id: item,
|
||||||
|
name: item,
|
||||||
|
version: item,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (fs.existsSync(versionJsonPath)) {
|
||||||
|
try {
|
||||||
|
const versionData = JSON.parse(
|
||||||
|
fs.readFileSync(versionJsonPath, 'utf8'),
|
||||||
|
);
|
||||||
|
versionInfo.version = versionData.id || item;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Ошибка при чтении файла версии ${item}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
versions.push(versionInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, versions };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении установленных версий:', error);
|
||||||
|
return { success: false, error: error.message, versions: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Регистрируем обработчик для get-installed-versions
|
||||||
|
ipcMain.handle('get-installed-versions', async () => {
|
||||||
|
return getInstalledVersions();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработчик get-available-versions использует функцию getInstalledVersions
|
||||||
|
ipcMain.handle('get-available-versions', async (event, { gistUrl }) => {
|
||||||
|
try {
|
||||||
|
// Используем URL из параметров или значение по умолчанию
|
||||||
|
const url =
|
||||||
|
gistUrl ||
|
||||||
|
'https://gist.githubusercontent.com/DIKER0K/06cd12fb3a4d08b1f0f8c763a7d05e06/raw/versions.json';
|
||||||
|
|
||||||
|
const response = await fetch(url);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch versions from Gist: ${response.status} ${response.statusText}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const versions = await response.json();
|
||||||
|
|
||||||
|
// Получаем уже установленные версии
|
||||||
|
const installedResult = getInstalledVersions();
|
||||||
|
const installedVersions = installedResult.success
|
||||||
|
? installedResult.versions
|
||||||
|
: [];
|
||||||
|
|
||||||
|
// Добавляем флаг installed к каждой версии
|
||||||
|
const versionsWithInstallStatus = versions.map((version: any) => {
|
||||||
|
const isInstalled = installedVersions.some(
|
||||||
|
(installed: any) => installed.id === version.id,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...version,
|
||||||
|
installed: isInstalled,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
versions: versionsWithInstallStatus,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении доступных версий:', error);
|
||||||
|
return { success: false, error: error.message, versions: [] };
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем обработчики IPC для аутентификации
|
// Добавляем обработчики IPC для аутентификации
|
||||||
@ -979,3 +1079,57 @@ export function initPackConfigHandlers() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Добавляем после обработчика get-available-versions
|
||||||
|
ipcMain.handle('get-version-config', async (event, { versionId }) => {
|
||||||
|
try {
|
||||||
|
const appPath = path.dirname(app.getPath('exe'));
|
||||||
|
const minecraftDir = path.join(appPath, '.minecraft');
|
||||||
|
const versionsDir = path.join(minecraftDir, 'versions');
|
||||||
|
const versionPath = path.join(versionsDir, versionId);
|
||||||
|
|
||||||
|
// Проверяем существование директории версии
|
||||||
|
if (!fs.existsSync(versionPath)) {
|
||||||
|
return { success: false, error: `Версия ${versionId} не найдена` };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проверяем конфигурационный файл версии
|
||||||
|
const configPath = path.join(versionPath, 'popa-launcher-config.json');
|
||||||
|
|
||||||
|
// Определяем базовые настройки по умолчанию
|
||||||
|
let config = {
|
||||||
|
downloadUrl: '',
|
||||||
|
apiReleaseUrl: '',
|
||||||
|
versionFileName: `${versionId}_version.txt`,
|
||||||
|
packName: versionId,
|
||||||
|
memory: 4096,
|
||||||
|
baseVersion: '1.21.4',
|
||||||
|
serverIp: 'popa-popa.ru',
|
||||||
|
fabricVersion: '0.16.14',
|
||||||
|
preserveFiles: ['popa-launcher-config.json'],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Если это Comfort, используем настройки по умолчанию
|
||||||
|
if (versionId === 'Comfort') {
|
||||||
|
config.downloadUrl =
|
||||||
|
'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip';
|
||||||
|
config.apiReleaseUrl =
|
||||||
|
'https://api.github.com/repos/DIKER0K/Comfort/releases/latest';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если есть конфигурационный файл, загружаем из него
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
try {
|
||||||
|
const savedConfig = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
|
config = { ...config, ...savedConfig };
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Ошибка чтения конфигурации ${versionId}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, config };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка получения настроек версии:', error);
|
||||||
|
return { success: false, error: error.message };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@ -11,7 +11,9 @@ export type Channels =
|
|||||||
| 'save-pack-config'
|
| 'save-pack-config'
|
||||||
| 'load-pack-config'
|
| 'load-pack-config'
|
||||||
| 'update-available'
|
| 'update-available'
|
||||||
| 'install-update';
|
| 'install-update'
|
||||||
|
| 'get-installed-versions'
|
||||||
|
| 'get-available-versions';
|
||||||
|
|
||||||
const electronHandler = {
|
const electronHandler = {
|
||||||
ipcRenderer: {
|
ipcRenderer: {
|
||||||
|
@ -47,3 +47,7 @@ h4 {
|
|||||||
h5 {
|
h5 {
|
||||||
font-family: 'Benzin-Bold' !important;
|
font-family: 'Benzin-Bold' !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-family: 'Benzin-Bold' !important;
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
Routes,
|
Routes,
|
||||||
Route,
|
Route,
|
||||||
Navigate,
|
Navigate,
|
||||||
|
useNavigate,
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import Login from './pages/Login';
|
import Login from './pages/Login';
|
||||||
import LaunchPage from './pages/LaunchPage';
|
import LaunchPage from './pages/LaunchPage';
|
||||||
@ -12,19 +13,7 @@ import TopBar from './components/TopBar';
|
|||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import MinecraftBackround from './components/MinecraftBackround';
|
import MinecraftBackround from './components/MinecraftBackround';
|
||||||
import { Notifier } from './components/Notifier';
|
import { Notifier } from './components/Notifier';
|
||||||
|
import { VersionsExplorer } from './pages/VersionsExplorer';
|
||||||
// Переместите launchOptions сюда, вне компонентов
|
|
||||||
const launchOptions = {
|
|
||||||
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', // Уберите префикс "fabric"
|
|
||||||
};
|
|
||||||
|
|
||||||
const AuthCheck = ({ children }: { children: ReactNode }) => {
|
const AuthCheck = ({ children }: { children: ReactNode }) => {
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
||||||
@ -54,15 +43,37 @@ const AuthCheck = ({ children }: { children: ReactNode }) => {
|
|||||||
|
|
||||||
const validateToken = async (token: string) => {
|
const validateToken = async (token: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://authserver.ely.by/auth/validate', {
|
// Используем IPC для валидации токена через main процесс
|
||||||
method: 'POST',
|
const result = await window.electron.ipcRenderer.invoke(
|
||||||
headers: {
|
'validate-token',
|
||||||
'Content-Type': 'application/json',
|
token,
|
||||||
},
|
);
|
||||||
body: JSON.stringify({ accessToken: token }),
|
|
||||||
});
|
// Если токен недействителен, очищаем сохраненные данные в localStorage
|
||||||
return response.ok;
|
if (!result.valid) {
|
||||||
|
console.log(
|
||||||
|
'Токен недействителен, очищаем данные авторизации из localStorage',
|
||||||
|
);
|
||||||
|
const savedConfig = localStorage.getItem('launcher_config');
|
||||||
|
if (savedConfig) {
|
||||||
|
const config = JSON.parse(savedConfig);
|
||||||
|
// Сохраняем только логин и другие настройки, но удаляем токены
|
||||||
|
const cleanedConfig = {
|
||||||
|
username: config.username,
|
||||||
|
memory: config.memory || 4096,
|
||||||
|
comfortVersion: config.comfortVersion || '',
|
||||||
|
password: '', // Очищаем пароль для безопасности
|
||||||
|
};
|
||||||
|
localStorage.setItem(
|
||||||
|
'launcher_config',
|
||||||
|
JSON.stringify(cleanedConfig),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.valid;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Ошибка проверки токена:', error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -91,6 +102,7 @@ const App = () => {
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
|
overflowX: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MinecraftBackround />
|
<MinecraftBackround />
|
||||||
@ -102,7 +114,15 @@ const App = () => {
|
|||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
<AuthCheck>
|
<AuthCheck>
|
||||||
<LaunchPage launchOptions={launchOptions} />
|
<VersionsExplorer />
|
||||||
|
</AuthCheck>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path="/launch/:versionId"
|
||||||
|
element={
|
||||||
|
<AuthCheck>
|
||||||
|
<LaunchPage />
|
||||||
</AuthCheck>
|
</AuthCheck>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
92
src/renderer/components/Settings/SettingsModal.tsx
Normal file
92
src/renderer/components/Settings/SettingsModal.tsx
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { Box, Typography, Button, Modal } from '@mui/material';
|
||||||
|
import React from 'react';
|
||||||
|
import MemorySlider from '../Login/MemorySlider';
|
||||||
|
import FilesSelector from '../FilesSelector';
|
||||||
|
|
||||||
|
interface SettingsModalProps {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
config: {
|
||||||
|
memory: number;
|
||||||
|
preserveFiles: string[];
|
||||||
|
};
|
||||||
|
onConfigChange: (newConfig: {
|
||||||
|
memory: number;
|
||||||
|
preserveFiles: string[];
|
||||||
|
}) => void;
|
||||||
|
packName: string;
|
||||||
|
onSave: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsModal = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
config,
|
||||||
|
onConfigChange,
|
||||||
|
packName,
|
||||||
|
onSave,
|
||||||
|
}: SettingsModalProps) => {
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
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={packName}
|
||||||
|
initialSelected={config.preserveFiles}
|
||||||
|
onSelectionChange={(selected) => {
|
||||||
|
onConfigChange({ ...config, preserveFiles: selected });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Typography variant="body1" sx={{ color: 'white' }}>
|
||||||
|
Оперативная память выделенная для Minecraft
|
||||||
|
</Typography>
|
||||||
|
<MemorySlider
|
||||||
|
memory={config.memory}
|
||||||
|
onChange={(e, value) => {
|
||||||
|
onConfigChange({ ...config, memory: value as number });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="success"
|
||||||
|
onClick={() => {
|
||||||
|
onSave();
|
||||||
|
onClose();
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
borderRadius: '3vw',
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Сохранить
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingsModal;
|
@ -1,7 +1,8 @@
|
|||||||
import { Box, Button, Typography } from '@mui/material';
|
import { Box, Button, Typography } from '@mui/material';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import MinimizeIcon from '@mui/icons-material/Minimize';
|
import MinimizeIcon from '@mui/icons-material/Minimize';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation, useNavigate } from 'react-router-dom';
|
||||||
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -24,6 +25,12 @@ export default function TopBar({ onRegister }: TopBarProps) {
|
|||||||
// Получаем текущий путь
|
// Получаем текущий путь
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isLoginPage = location.pathname === '/login';
|
const isLoginPage = location.pathname === '/login';
|
||||||
|
const isLaunchPage = location.pathname.startsWith('/launch');
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleLaunchPage = () => {
|
||||||
|
navigate('/');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@ -38,9 +45,37 @@ export default function TopBar({ onRegister }: TopBarProps) {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
WebkitAppRegion: 'drag',
|
WebkitAppRegion: 'drag',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
justifyContent: 'flex-end', // Всё содержимое справа
|
justifyContent: 'space-between', // Всё содержимое справа
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
WebkitAppRegion: 'no-drag',
|
||||||
|
gap: '2vw',
|
||||||
|
padding: '1em',
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLaunchPage && (
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
onClick={() => handleLaunchPage()}
|
||||||
|
sx={{
|
||||||
|
width: '3em',
|
||||||
|
height: '3em',
|
||||||
|
borderRadius: '50%',
|
||||||
|
border: 'unset',
|
||||||
|
color: 'white',
|
||||||
|
minWidth: 'unset',
|
||||||
|
minHeight: 'unset',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
{/* Правая часть со всеми кнопками */}
|
{/* Правая часть со всеми кнопками */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -5,16 +5,14 @@ import {
|
|||||||
Snackbar,
|
Snackbar,
|
||||||
Alert,
|
Alert,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
Modal,
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import ServerStatus from '../components/ServerStatus/ServerStatus';
|
import ServerStatus from '../components/ServerStatus/ServerStatus';
|
||||||
import PopaPopa from '../components/popa-popa';
|
import PopaPopa from '../components/popa-popa';
|
||||||
import SettingsIcon from '@mui/icons-material/Settings';
|
import SettingsIcon from '@mui/icons-material/Settings';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import MemorySlider from '../components/Login/MemorySlider';
|
import SettingsModal from '../components/Settings/SettingsModal';
|
||||||
import FilesSelector from '../components/FilesSelector';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
@ -30,7 +28,9 @@ declare global {
|
|||||||
|
|
||||||
// Определяем тип для props
|
// Определяем тип для props
|
||||||
interface LaunchPageProps {
|
interface LaunchPageProps {
|
||||||
launchOptions: {
|
onLaunchPage?: () => void;
|
||||||
|
launchOptions?: {
|
||||||
|
// Делаем опциональным
|
||||||
downloadUrl: string;
|
downloadUrl: string;
|
||||||
apiReleaseUrl: string;
|
apiReleaseUrl: string;
|
||||||
versionFileName: string;
|
versionFileName: string;
|
||||||
@ -42,8 +42,14 @@ interface LaunchPageProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
const LaunchPage = ({
|
||||||
|
onLaunchPage,
|
||||||
|
launchOptions = {} as any,
|
||||||
|
}: LaunchPageProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { versionId } = useParams();
|
||||||
|
const [versionConfig, setVersionConfig] = useState<any>(null);
|
||||||
|
|
||||||
// Начальное состояние должно быть пустым или с минимальными значениями
|
// Начальное состояние должно быть пустым или с минимальными значениями
|
||||||
const [config, setConfig] = useState<{
|
const [config, setConfig] = useState<{
|
||||||
memory: number;
|
memory: number;
|
||||||
@ -101,27 +107,82 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
|||||||
}, [navigate]);
|
}, [navigate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Загрузка конфигурации сборки при монтировании
|
const fetchVersionConfig = async () => {
|
||||||
const loadPackConfig = async () => {
|
if (!versionId) return;
|
||||||
|
|
||||||
try {
|
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(
|
const result = await window.electron.ipcRenderer.invoke(
|
||||||
'load-pack-config',
|
'get-version-config',
|
||||||
{
|
{ versionId },
|
||||||
packName: launchOptions.packName,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.success && result.config) {
|
if (result.success) {
|
||||||
// Полностью заменяем config значениями из файла
|
setVersionConfig(result.config);
|
||||||
setConfig(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) {
|
} catch (error) {
|
||||||
console.error('Ошибка при загрузке настроек:', 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 || [],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
loadPackConfig();
|
fetchVersionConfig();
|
||||||
}, [launchOptions.packName]);
|
}, [versionId]);
|
||||||
|
|
||||||
const showNotification = (
|
const showNotification = (
|
||||||
message: string,
|
message: string,
|
||||||
@ -134,94 +195,86 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
|||||||
setNotification({ ...notification, open: false });
|
setNotification({ ...notification, open: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Функция для запуска игры с настройками выбранной версии
|
||||||
const handleLaunchMinecraft = async () => {
|
const handleLaunchMinecraft = async () => {
|
||||||
try {
|
try {
|
||||||
setIsDownloading(true);
|
setIsDownloading(true);
|
||||||
setDownloadProgress(0);
|
setDownloadProgress(0);
|
||||||
setBuffer(10);
|
setBuffer(10);
|
||||||
|
|
||||||
// Загружаем настройки сборки
|
// Используем настройки выбранной версии или дефолтные
|
||||||
const result = await window.electron.ipcRenderer.invoke(
|
const currentConfig = versionConfig || {
|
||||||
'load-pack-config',
|
packName: versionId || 'Comfort',
|
||||||
{
|
memory: 4096,
|
||||||
packName: launchOptions.packName,
|
baseVersion: '1.21.4',
|
||||||
},
|
serverIp: 'popa-popa.ru',
|
||||||
);
|
fabricVersion: '0.16.14',
|
||||||
|
preserveFiles: [],
|
||||||
|
};
|
||||||
|
|
||||||
// Используйте уже существующий state вместо локальной переменной
|
// Проверяем, является ли это ванильной версией
|
||||||
if (result.success && result.config) {
|
const isVanillaVersion =
|
||||||
setConfig(result.config); // Обновляем state
|
!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(
|
const savedConfig = JSON.parse(
|
||||||
localStorage.getItem('launcher_config') || '{}',
|
localStorage.getItem('launcher_config') || '{}',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Опции для скачивания сборки
|
const options = {
|
||||||
const packOptions = {
|
accessToken: savedConfig.accessToken,
|
||||||
downloadUrl: launchOptions.downloadUrl,
|
uuid: savedConfig.uuid,
|
||||||
apiReleaseUrl: launchOptions.apiReleaseUrl,
|
username: savedConfig.username,
|
||||||
versionFileName: launchOptions.versionFileName,
|
memory: config.memory,
|
||||||
packName: launchOptions.packName,
|
baseVersion: currentConfig.baseVersion,
|
||||||
preserveFiles: config.preserveFiles,
|
packName: versionId || currentConfig.packName,
|
||||||
|
serverIp: currentConfig.serverIp,
|
||||||
|
fabricVersion: currentConfig.fabricVersion,
|
||||||
|
// Для ванильной версии устанавливаем флаг
|
||||||
|
isVanillaVersion: isVanillaVersion,
|
||||||
|
versionToLaunchOverride: isVanillaVersion ? versionId : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Передаем опции для скачивания
|
const launchResult = await window.electron.ipcRenderer.invoke(
|
||||||
const downloadResult = await window.electron.ipcRenderer.invoke(
|
'launch-minecraft',
|
||||||
'download-and-extract',
|
options,
|
||||||
packOptions,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (downloadResult?.success) {
|
if (launchResult?.success) {
|
||||||
let needsSecondAttempt = false;
|
showNotification('Minecraft успешно запущен!', 'success');
|
||||||
|
|
||||||
if (downloadResult.updated) {
|
|
||||||
showNotification(
|
|
||||||
`Сборка ${downloadResult.packName} успешно обновлена до версии ${downloadResult.version}`,
|
|
||||||
'success',
|
|
||||||
);
|
|
||||||
needsSecondAttempt = true;
|
|
||||||
} else {
|
|
||||||
showNotification(
|
|
||||||
`Установлена актуальная версия сборки ${downloadResult.packName} (${downloadResult.version})`,
|
|
||||||
'info',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Опции для запуска
|
|
||||||
const options = {
|
|
||||||
accessToken: savedConfig.accessToken,
|
|
||||||
uuid: savedConfig.uuid,
|
|
||||||
username: savedConfig.username,
|
|
||||||
memory: config.memory, // Используем state
|
|
||||||
baseVersion: launchOptions.baseVersion,
|
|
||||||
packName: launchOptions.packName,
|
|
||||||
serverIp: launchOptions.serverIp,
|
|
||||||
fabricVersion: launchOptions.fabricVersion,
|
|
||||||
};
|
|
||||||
|
|
||||||
const launchResult = await window.electron.ipcRenderer.invoke(
|
|
||||||
'launch-minecraft',
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (needsSecondAttempt) {
|
|
||||||
showNotification(
|
|
||||||
'Завершаем настройку компонентов, повторный запуск...',
|
|
||||||
'info',
|
|
||||||
);
|
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
||||||
|
|
||||||
const secondAttempt = await window.electron.ipcRenderer.invoke(
|
|
||||||
'launch-minecraft',
|
|
||||||
options,
|
|
||||||
);
|
|
||||||
|
|
||||||
showNotification('Minecraft успешно запущен!', 'success');
|
|
||||||
} else if (launchResult?.success) {
|
|
||||||
showNotification('Minecraft успешно запущен!', 'success');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
@ -240,7 +293,7 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await window.electron.ipcRenderer.invoke('save-pack-config', {
|
await window.electron.ipcRenderer.invoke('save-pack-config', {
|
||||||
packName: launchOptions.packName,
|
packName: versionId || versionConfig?.packName || 'Comfort',
|
||||||
config: configToSave,
|
config: configToSave,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -285,7 +338,7 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
|||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
<ServerStatus
|
<ServerStatus
|
||||||
serverIp={launchOptions.serverIp}
|
serverIp={versionConfig?.serverIp || 'popa-popa.ru'}
|
||||||
refreshInterval={30000}
|
refreshInterval={30000}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -365,65 +418,14 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
|||||||
</Alert>
|
</Alert>
|
||||||
</Snackbar>
|
</Snackbar>
|
||||||
|
|
||||||
<Modal
|
<SettingsModal
|
||||||
open={open}
|
open={open}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
aria-labelledby="modal-modal-title"
|
config={config}
|
||||||
aria-describedby="modal-modal-description"
|
onConfigChange={setConfig}
|
||||||
>
|
packName={versionId || versionConfig?.packName || 'Comfort'}
|
||||||
<Box
|
onSave={savePackConfig}
|
||||||
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={launchOptions.packName}
|
|
||||||
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>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -38,13 +38,25 @@ const Login = () => {
|
|||||||
console.log(
|
console.log(
|
||||||
'Не удалось обновить токен, требуется новая авторизация',
|
'Не удалось обновить токен, требуется новая авторизация',
|
||||||
);
|
);
|
||||||
const newSession = await authenticateWithElyBy(
|
// Очищаем недействительные токены
|
||||||
config.username,
|
saveConfig({
|
||||||
config.password,
|
accessToken: '',
|
||||||
saveConfig,
|
clientToken: '',
|
||||||
);
|
});
|
||||||
if (!newSession) {
|
|
||||||
console.log('Авторизация не удалась');
|
// Пытаемся выполнить новую авторизацию
|
||||||
|
if (config.password) {
|
||||||
|
const newSession = await authenticateWithElyBy(
|
||||||
|
config.username,
|
||||||
|
config.password,
|
||||||
|
saveConfig,
|
||||||
|
);
|
||||||
|
if (!newSession) {
|
||||||
|
console.log('Авторизация не удалась');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('Требуется ввод пароля для новой авторизации');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -53,6 +65,13 @@ const Login = () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Токен отсутствует, выполняем авторизацию...');
|
console.log('Токен отсутствует, выполняем авторизацию...');
|
||||||
|
// Проверяем наличие пароля
|
||||||
|
if (!config.password) {
|
||||||
|
console.log('Ошибка: не указан пароль');
|
||||||
|
alert('Введите пароль!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const session = await authenticateWithElyBy(
|
const session = await authenticateWithElyBy(
|
||||||
config.username,
|
config.username,
|
||||||
config.password,
|
config.password,
|
||||||
@ -68,6 +87,11 @@ const Login = () => {
|
|||||||
navigate('/');
|
navigate('/');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`ОШИБКА при авторизации: ${error.message}`);
|
console.log(`ОШИБКА при авторизации: ${error.message}`);
|
||||||
|
// Очищаем недействительные токены при ошибке
|
||||||
|
saveConfig({
|
||||||
|
accessToken: '',
|
||||||
|
clientToken: '',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
394
src/renderer/pages/VersionsExplorer.tsx
Normal file
394
src/renderer/pages/VersionsExplorer.tsx
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
CardMedia,
|
||||||
|
CardContent,
|
||||||
|
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;
|
||||||
|
name: string;
|
||||||
|
imageUrl: string;
|
||||||
|
version: string;
|
||||||
|
onSelect: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VersionCard: React.FC<VersionCardProps> = ({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
imageUrl,
|
||||||
|
version,
|
||||||
|
onSelect,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<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',
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
onClick={() => onSelect(id)}
|
||||||
|
>
|
||||||
|
<CardContent
|
||||||
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '10%',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
gutterBottom
|
||||||
|
variant="h5"
|
||||||
|
component="div"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#ffffff',
|
||||||
|
fontSize: '1.5rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface VersionInfo {
|
||||||
|
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[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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 [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 {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Получаем список установленных версий через IPC
|
||||||
|
const installedResult = await window.electron.ipcRenderer.invoke(
|
||||||
|
'get-installed-versions',
|
||||||
|
);
|
||||||
|
if (installedResult.success) {
|
||||||
|
setInstalledVersions(installedResult.versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получаем доступные версии с 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);
|
||||||
|
// Можно добавить обработку ошибки, например показать уведомление
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchVersions();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSelectVersion = (version: VersionInfo) => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'selected_version_config',
|
||||||
|
JSON.stringify(version.config || {}),
|
||||||
|
);
|
||||||
|
navigate(`/launch/${version.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
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={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
paddingLeft: '5vw',
|
||||||
|
paddingRight: '5vw',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Box display="flex" justifyContent="center" my={5}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={3}
|
||||||
|
sx={{
|
||||||
|
width: '100%',
|
||||||
|
overflowY: 'auto',
|
||||||
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Показываем установленные версии или дефолтную, если она есть */}
|
||||||
|
{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>
|
||||||
|
);
|
||||||
|
};
|
Reference in New Issue
Block a user