From 815ce286f735a4f719408110deb21b284dfe25c8 Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Tue, 8 Jul 2025 03:29:36 +0500 Subject: [PATCH] add: VersionExplorer, don't work first run Minecraft --- src/main/minecraft-launcher.ts | 101 +++++++++ src/renderer/App.tsx | 25 +-- src/renderer/components/TopBar.tsx | 39 +++- src/renderer/pages/LaunchPage.tsx | 232 ++++++++++++-------- src/renderer/pages/VersionsExplorer.tsx | 268 ++++++++++++++++++++++++ 5 files changed, 559 insertions(+), 106 deletions(-) create mode 100644 src/renderer/pages/VersionsExplorer.tsx diff --git a/src/main/minecraft-launcher.ts b/src/main/minecraft-launcher.ts index e08ae82..50a55eb 100644 --- a/src/main/minecraft-launcher.ts +++ b/src/main/minecraft-launcher.ts @@ -804,6 +804,53 @@ export function initMinecraftHandlers() { return { success: false, error: error.message }; } }); + + // Добавьте в функцию initMinecraftHandlers новый обработчик + ipcMain.handle('get-available-versions', async (event) => { + 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 }; + } + }); } // Добавляем обработчики IPC для аутентификации @@ -979,3 +1026,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 }; + } +}); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 0b5eefc..c2f5f7f 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -3,6 +3,7 @@ import { Routes, Route, Navigate, + useNavigate, } from 'react-router-dom'; import Login from './pages/Login'; import LaunchPage from './pages/LaunchPage'; @@ -12,19 +13,7 @@ import TopBar from './components/TopBar'; import { Box } from '@mui/material'; import MinecraftBackround from './components/MinecraftBackround'; import { Notifier } from './components/Notifier'; - -// Переместите 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" -}; +import { VersionsExplorer } from './pages/VersionsExplorer'; const AuthCheck = ({ children }: { children: ReactNode }) => { const [isAuthenticated, setIsAuthenticated] = useState(null); @@ -102,7 +91,15 @@ const App = () => { path="/" element={ - + + + } + /> + + } /> diff --git a/src/renderer/components/TopBar.tsx b/src/renderer/components/TopBar.tsx index fb2e8e3..bc361f5 100644 --- a/src/renderer/components/TopBar.tsx +++ b/src/renderer/components/TopBar.tsx @@ -1,7 +1,8 @@ import { Box, Button, Typography } from '@mui/material'; import CloseIcon from '@mui/icons-material/Close'; 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 { interface Window { @@ -24,6 +25,12 @@ export default function TopBar({ onRegister }: TopBarProps) { // Получаем текущий путь const location = useLocation(); const isLoginPage = location.pathname === '/login'; + const isLaunchPage = location.pathname.startsWith('/launch'); + const navigate = useNavigate(); + + const handleLaunchPage = () => { + navigate('/'); + }; return ( + + {isLaunchPage && ( + + )} + {/* Правая часть со всеми кнопками */} void; + launchOptions?: { + // Делаем опциональным downloadUrl: string; apiReleaseUrl: string; versionFileName: string; @@ -42,8 +44,11 @@ interface LaunchPageProps { }; } -const LaunchPage = ({ launchOptions }: LaunchPageProps) => { +const LaunchPage = ({ onLaunchPage, launchOptions = {} as any }: LaunchPageProps) => { const navigate = useNavigate(); + const { versionId } = useParams(); + const [versionConfig, setVersionConfig] = useState(null); + // Начальное состояние должно быть пустым или с минимальными значениями const [config, setConfig] = useState<{ memory: number; @@ -101,27 +106,82 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => { }, [navigate]); useEffect(() => { - // Загрузка конфигурации сборки при монтировании - const loadPackConfig = async () => { + 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( - 'load-pack-config', - { - packName: launchOptions.packName, - }, + 'get-version-config', + { versionId }, ); - if (result.success && result.config) { - // Полностью заменяем config значениями из файла - setConfig(result.config); + 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); + 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(); - }, [launchOptions.packName]); + fetchVersionConfig(); + }, [versionId]); const showNotification = ( message: string, @@ -134,94 +194,86 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => { setNotification({ ...notification, open: false }); }; + // Функция для запуска игры с настройками выбранной версии const handleLaunchMinecraft = async () => { try { setIsDownloading(true); setDownloadProgress(0); setBuffer(10); - // Загружаем настройки сборки - const result = await window.electron.ipcRenderer.invoke( - 'load-pack-config', - { - packName: launchOptions.packName, - }, - ); + // Используем настройки выбранной версии или дефолтные + const currentConfig = versionConfig || { + packName: versionId || 'Comfort', + memory: 4096, + baseVersion: '1.21.4', + serverIp: 'popa-popa.ru', + fabricVersion: '0.16.14', + preserveFiles: [], + }; - // Используйте уже существующий state вместо локальной переменной - if (result.success && result.config) { - setConfig(result.config); // Обновляем state + // Проверяем, является ли это ванильной версией + 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 packOptions = { - downloadUrl: launchOptions.downloadUrl, - apiReleaseUrl: launchOptions.apiReleaseUrl, - versionFileName: launchOptions.versionFileName, - packName: launchOptions.packName, - preserveFiles: config.preserveFiles, + 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 downloadResult = await window.electron.ipcRenderer.invoke( - 'download-and-extract', - packOptions, + const launchResult = await window.electron.ipcRenderer.invoke( + 'launch-minecraft', + options, ); - if (downloadResult?.success) { - let needsSecondAttempt = false; - - 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'); - } + if (launchResult?.success) { + showNotification('Minecraft успешно запущен!', 'success'); } } catch (error) { console.error('Error:', error); @@ -240,7 +292,7 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => { }; await window.electron.ipcRenderer.invoke('save-pack-config', { - packName: launchOptions.packName, + packName: versionId || versionConfig?.packName || 'Comfort', config: configToSave, }); @@ -285,7 +337,7 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => { @@ -393,8 +445,8 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => { Файлы и папки, которые будут сохранены после переустановки сборки { setConfig((prev) => ({ ...prev, preserveFiles: selected })); }} diff --git a/src/renderer/pages/VersionsExplorer.tsx b/src/renderer/pages/VersionsExplorer.tsx new file mode 100644 index 0000000..265fc1d --- /dev/null +++ b/src/renderer/pages/VersionsExplorer.tsx @@ -0,0 +1,268 @@ +import { useEffect, useState } from 'react'; +import { + Box, + Typography, + Grid, + Card, + CardMedia, + CardContent, + CardActions, + Button, + CircularProgress, +} from '@mui/material'; +import { useNavigate } from 'react-router-dom'; + +interface VersionCardProps { + id: string; + name: string; + imageUrl: string; + version: string; + onSelect: (id: string) => void; +} + +const VersionCard: React.FC = ({ + id, + name, + imageUrl, + version, + onSelect, +}) => { + return ( + + + + + + {version} + + + + + + + {name} + + + Версия: {version} + + + + + + + + ); +}; + +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[]; + }; +} + +// В компоненте VersionsExplorer +export const VersionsExplorer = () => { + const [versions, setVersions] = useState([]); + const [loading, setLoading] = useState(true); + const navigate = useNavigate(); + + useEffect(() => { + const fetchVersions = async () => { + try { + const result = await window.electron.ipcRenderer.invoke( + 'get-available-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 }, + ); + + return { + ...version, + config: configResult.success ? configResult.config : undefined, + }; + }), + ); + + setVersions(versionsWithConfig); + } else { + console.error('Ошибка получения версий:', result.error); + } + } catch (error) { + console.error('Ошибка при запросе версий:', error); + } finally { + setLoading(false); + } + }; + + fetchVersions(); + }, []); + + const handleSelectVersion = (version: VersionInfo) => { + // Сохраняем конфигурацию в localStorage для использования в LaunchPage + localStorage.setItem( + 'selected_version_config', + JSON.stringify(version.config || {}), + ); + 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'], + }, + }, + ]; + + return ( + + + Доступные версии + + + {loading ? ( + + + + ) : ( + + {displayVersions.map((version) => ( + + handleSelectVersion(version)} + /> + + ))} + + )} + + ); +};