Working version(some)
This commit is contained in:
463
src/main/main.ts
463
src/main/main.ts
@ -14,6 +14,121 @@ import { autoUpdater } from 'electron-updater';
|
||||
import log from 'electron-log';
|
||||
import MenuBuilder from './menu';
|
||||
import { resolveHtmlPath } from './util';
|
||||
import fs from 'fs';
|
||||
import https from 'https';
|
||||
import extract from 'extract-zip';
|
||||
import { launch, Version, diagnose } from '@xmcl/core';
|
||||
import { execSync } from 'child_process';
|
||||
import {
|
||||
installDependencies,
|
||||
installFabric,
|
||||
getFabricLoaders,
|
||||
getVersionList,
|
||||
install,
|
||||
installTask,
|
||||
installDependenciesTask,
|
||||
} from '@xmcl/installer';
|
||||
import { Task } from '@xmcl/task';
|
||||
// import findJavaHome from 'find-java-home';
|
||||
|
||||
// Функция для поиска Java
|
||||
async function findJava(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
// 1. Сначала проверяем переменную JAVA_HOME
|
||||
if (process.env.JAVA_HOME) {
|
||||
const javaPath = path.join(
|
||||
process.env.JAVA_HOME,
|
||||
'bin',
|
||||
'java' + (process.platform === 'win32' ? '.exe' : ''),
|
||||
);
|
||||
if (fs.existsSync(javaPath)) {
|
||||
return resolve(javaPath);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Проверяем стандартные пути установки в зависимости от платформы
|
||||
const checkPaths: string[] = [];
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Windows
|
||||
const programFiles = process.env['ProgramFiles'] || 'C:\\Program Files';
|
||||
const programFilesX86 =
|
||||
process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)';
|
||||
|
||||
// JDK пути
|
||||
[
|
||||
'Java',
|
||||
'AdoptOpenJDK',
|
||||
'Eclipse Adoptium',
|
||||
'BellSoft',
|
||||
'Zulu',
|
||||
'Amazon Corretto',
|
||||
].forEach((vendor) => {
|
||||
checkPaths.push(path.join(programFiles, vendor));
|
||||
checkPaths.push(path.join(programFilesX86, vendor));
|
||||
});
|
||||
} else if (process.platform === 'darwin') {
|
||||
// macOS
|
||||
checkPaths.push('/Library/Java/JavaVirtualMachines');
|
||||
checkPaths.push('/System/Library/Java/JavaVirtualMachines');
|
||||
checkPaths.push('/usr/libexec/java_home');
|
||||
} else {
|
||||
// Linux
|
||||
checkPaths.push('/usr/lib/jvm');
|
||||
checkPaths.push('/usr/java');
|
||||
checkPaths.push('/opt/java');
|
||||
}
|
||||
|
||||
// Проверяем каждый путь
|
||||
for (const basePath of checkPaths) {
|
||||
if (fs.existsSync(basePath)) {
|
||||
try {
|
||||
// Находим подпапки с JDK/JRE
|
||||
const entries = fs.readdirSync(basePath, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory()) {
|
||||
// Проверяем наличие исполняемого файла java в bin
|
||||
const javaPath = path.join(
|
||||
basePath,
|
||||
entry.name,
|
||||
'bin',
|
||||
'java' + (process.platform === 'win32' ? '.exe' : ''),
|
||||
);
|
||||
|
||||
if (fs.existsSync(javaPath)) {
|
||||
return resolve(javaPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`Ошибка при сканировании ${basePath}:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Пробуем найти java в PATH через команду which/where
|
||||
try {
|
||||
const command =
|
||||
process.platform === 'win32' ? 'where java' : 'which java';
|
||||
const javaPath = execSync(command).toString().trim();
|
||||
|
||||
if (javaPath && fs.existsSync(javaPath)) {
|
||||
return resolve(javaPath);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Ошибка при поиске java через PATH:', err);
|
||||
}
|
||||
|
||||
// Если Java не найдена, выдаем ошибку
|
||||
reject(
|
||||
new Error('Java не найдена. Установите Java и повторите попытку.'),
|
||||
);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
class AppUpdater {
|
||||
constructor() {
|
||||
@ -43,6 +158,354 @@ if (isDebug) {
|
||||
require('electron-debug').default();
|
||||
}
|
||||
|
||||
// Minecraft
|
||||
|
||||
const COMFORT_DOWNLOAD_URL =
|
||||
'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip';
|
||||
const GITHUB_API_RELEASE_URL =
|
||||
'https://api.github.com/repos/DIKER0K/Comfort/releases/latest';
|
||||
const COMFORT_VERSION_FILE = 'comfort_version.txt';
|
||||
|
||||
async function getLatestReleaseVersion(): Promise<string> {
|
||||
try {
|
||||
const response = await fetch(GITHUB_API_RELEASE_URL);
|
||||
const data = await response.json();
|
||||
return data.tag_name || '0.0.0';
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch latest version:', error);
|
||||
return '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
// Модифицируем обработчик IPC
|
||||
ipcMain.handle('download-and-extract', async (event) => {
|
||||
try {
|
||||
const appPath = path.dirname(app.getPath('exe'));
|
||||
const minecraftDir = path.join(appPath, '.minecraft');
|
||||
const versionsDir = path.join(minecraftDir, 'versions');
|
||||
const versionFilePath = path.join(minecraftDir, COMFORT_VERSION_FILE);
|
||||
|
||||
// Получаем текущую и последнюю версии
|
||||
const latestVersion = await getLatestReleaseVersion();
|
||||
let currentVersion = '';
|
||||
|
||||
// Проверяем текущую версию, если файл существует
|
||||
if (fs.existsSync(versionFilePath)) {
|
||||
currentVersion = fs.readFileSync(versionFilePath, 'utf-8').trim();
|
||||
}
|
||||
|
||||
// Проверяем, нужно ли обновление
|
||||
if (currentVersion === latestVersion) {
|
||||
return { success: true, updated: false, version: currentVersion };
|
||||
}
|
||||
|
||||
const tempDir = path.join(appPath, 'temp');
|
||||
|
||||
// Создаем/очищаем временную директорию
|
||||
if (fs.existsSync(tempDir)) {
|
||||
fs.rmSync(tempDir, { recursive: true });
|
||||
}
|
||||
fs.mkdirSync(tempDir, { recursive: true });
|
||||
|
||||
const zipPath = path.join(tempDir, 'Comfort.zip');
|
||||
|
||||
// Скачиваем файл
|
||||
await downloadFile(COMFORT_DOWNLOAD_URL, zipPath, (progress) => {
|
||||
event.sender.send('download-progress', progress);
|
||||
});
|
||||
|
||||
// Проверяем архив
|
||||
const fileStats = fs.statSync(zipPath);
|
||||
if (fileStats.size < 1024) {
|
||||
throw new Error('Downloaded file is too small, likely corrupted');
|
||||
}
|
||||
|
||||
// Создаем папку versions если её нет
|
||||
if (!fs.existsSync(versionsDir)) {
|
||||
fs.mkdirSync(versionsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Распаковываем архив напрямую в папку versions
|
||||
await extract(zipPath, { dir: versionsDir });
|
||||
fs.unlinkSync(zipPath);
|
||||
|
||||
// Сохраняем новую версию
|
||||
fs.writeFileSync(versionFilePath, latestVersion);
|
||||
|
||||
// Удаляем временную директорию
|
||||
fs.rmSync(tempDir, { recursive: true });
|
||||
|
||||
// После распаковки архива и перед запуском
|
||||
const versionsContents = fs.readdirSync(versionsDir);
|
||||
console.log('Доступные версии:', versionsContents);
|
||||
|
||||
return { success: true, updated: true, version: latestVersion };
|
||||
} catch (error) {
|
||||
console.error('Error in download-and-extract:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
async function downloadFile(
|
||||
url: string,
|
||||
dest: string,
|
||||
progressCallback: (progress: number) => void,
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const file = fs.createWriteStream(dest);
|
||||
let downloadedSize = 0;
|
||||
let totalSize = 0;
|
||||
let redirectCount = 0;
|
||||
|
||||
const makeRequest = (requestUrl: string) => {
|
||||
https
|
||||
.get(requestUrl, (response) => {
|
||||
// Обрабатываем редиректы
|
||||
if (response.statusCode === 301 || response.statusCode === 302) {
|
||||
if (redirectCount++ > 5) {
|
||||
reject(new Error('Too many redirects'));
|
||||
return;
|
||||
}
|
||||
makeRequest(response.headers.location!);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.statusCode !== 200) {
|
||||
reject(new Error(`Server returned ${response.statusCode}`));
|
||||
return;
|
||||
}
|
||||
|
||||
totalSize = parseInt(response.headers['content-length'] || '0', 10);
|
||||
|
||||
response.on('data', (chunk) => {
|
||||
downloadedSize += chunk.length;
|
||||
const progress = Math.round((downloadedSize / totalSize) * 100);
|
||||
progressCallback(progress);
|
||||
});
|
||||
|
||||
response.pipe(file);
|
||||
|
||||
file.on('finish', () => {
|
||||
file.close();
|
||||
// Проверяем, что файл скачан полностью
|
||||
if (downloadedSize !== totalSize && totalSize > 0) {
|
||||
fs.unlink(dest, () => {
|
||||
reject(new Error('File download incomplete'));
|
||||
});
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
})
|
||||
.on('error', (err) => {
|
||||
fs.unlink(dest, () => reject(err));
|
||||
});
|
||||
};
|
||||
|
||||
makeRequest(url);
|
||||
});
|
||||
}
|
||||
|
||||
ipcMain.handle('launch-minecraft', async (event) => {
|
||||
try {
|
||||
const baseVersion = '1.21.4';
|
||||
const appPath = path.dirname(app.getPath('exe'));
|
||||
const minecraftDir = path.join(appPath, '.minecraft');
|
||||
const versionsDir = path.join(minecraftDir, 'versions');
|
||||
|
||||
// Определяем версию для запуска
|
||||
const versionsContents = fs.readdirSync(versionsDir);
|
||||
console.log('Доступные версии:', versionsContents);
|
||||
|
||||
// Найти версию Comfort или версию с Fabric
|
||||
let versionToLaunch = '';
|
||||
if (versionsContents.includes('1.21.4-fabric0.16.14')) {
|
||||
// Использовать стандартную версию Fabric вместо Comfort
|
||||
versionToLaunch = '1.21.4-fabric0.16.14';
|
||||
} else if (versionsContents.includes('Comfort')) {
|
||||
versionToLaunch = 'Comfort';
|
||||
} else {
|
||||
// Запасной вариант
|
||||
versionToLaunch = '1.21.4';
|
||||
}
|
||||
|
||||
console.log('Запускаем версию:', versionToLaunch);
|
||||
|
||||
// Находим путь к Java
|
||||
event.sender.send('installation-status', {
|
||||
step: 'java',
|
||||
message: 'Поиск Java...',
|
||||
});
|
||||
const javaPath = await findJava();
|
||||
|
||||
// 1. Получаем список версий и устанавливаем ванильный Minecraft
|
||||
event.sender.send('installation-status', {
|
||||
step: 'minecraft-list',
|
||||
message: 'Получение списка версий Minecraft...',
|
||||
});
|
||||
const versionList = await getVersionList();
|
||||
const minecraftVersion = versionList.versions.find(
|
||||
(v) => v.id === baseVersion,
|
||||
);
|
||||
if (!minecraftVersion) {
|
||||
throw new Error(`Minecraft версия ${baseVersion} не найдена`);
|
||||
}
|
||||
|
||||
// Устанавливаем базовую версию Minecraft
|
||||
event.sender.send('installation-status', {
|
||||
step: 'minecraft-install',
|
||||
message: `Установка Minecraft ${baseVersion}...`,
|
||||
});
|
||||
|
||||
// Переименовываем переменную, чтобы избежать конфликта с функцией
|
||||
const installMcTask = installTask(minecraftVersion, minecraftDir, {
|
||||
skipRevalidate: true, // Пропускать повторную проверку
|
||||
});
|
||||
await installMcTask.startAndWait({
|
||||
onStart(task) {
|
||||
event.sender.send('installation-status', {
|
||||
step: `minecraft-install.${task.path}`,
|
||||
message: `Начало: ${task.name || task.path}`,
|
||||
});
|
||||
},
|
||||
onUpdate(task) {
|
||||
// Убираем неиспользуемый параметр chunkSize
|
||||
// Обновляем общий прогресс
|
||||
const percentage =
|
||||
Math.round((installMcTask.progress / installMcTask.total) * 100) || 0;
|
||||
|
||||
event.sender.send('download-progress', percentage);
|
||||
event.sender.send('installation-status', {
|
||||
step: `minecraft-install.${task.path}`,
|
||||
message: `Прогресс ${task.name || task.path}: ${percentage}% (${installMcTask.progress}/${installMcTask.total})`,
|
||||
});
|
||||
},
|
||||
onFailed(task, error: Error) {
|
||||
// Добавляем типизацию
|
||||
event.sender.send('installation-status', {
|
||||
step: `minecraft-install.${task.path}`,
|
||||
message: `Ошибка: ${error.message}`,
|
||||
});
|
||||
console.error(`Ошибка при установке ${task.path}:`, error);
|
||||
},
|
||||
onSucceed(task) {
|
||||
// Убираем неиспользуемый параметр result
|
||||
event.sender.send('installation-status', {
|
||||
step: `minecraft-install.${task.path}`,
|
||||
message: `Завершено: ${task.name || task.path}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 2. Устанавливаем Fabric
|
||||
event.sender.send('installation-status', {
|
||||
step: 'fabric-list',
|
||||
message: 'Получение списка версий Fabric...',
|
||||
});
|
||||
const fabricVersions = await getFabricLoaders();
|
||||
const fabricVersion = fabricVersions[0]; // Последняя версия
|
||||
|
||||
if (!fabricVersion) {
|
||||
throw new Error(`Не найдены версии Fabric`);
|
||||
}
|
||||
|
||||
event.sender.send('installation-status', {
|
||||
step: 'fabric-install',
|
||||
message: `Установка Fabric ${fabricVersion.version}...`,
|
||||
});
|
||||
await installFabric({
|
||||
minecraftVersion: baseVersion,
|
||||
version: fabricVersion.version,
|
||||
minecraft: minecraftDir,
|
||||
});
|
||||
|
||||
// 3. Используем идентификатор Fabric-версии
|
||||
const fabricVersionId = `${baseVersion}-fabric${fabricVersion.version}`;
|
||||
|
||||
// 4. Разрешаем и устанавливаем зависимости
|
||||
event.sender.send('installation-status', {
|
||||
step: 'version-parse',
|
||||
message: 'Подготовка версии...',
|
||||
});
|
||||
const resolvedVersion = await Version.parse(minecraftDir, fabricVersionId);
|
||||
|
||||
event.sender.send('installation-status', {
|
||||
step: 'dependencies',
|
||||
message: 'Установка библиотек и ресурсов...',
|
||||
});
|
||||
const depsTask = installDependenciesTask(resolvedVersion, {
|
||||
assetsDownloadConcurrency: 4,
|
||||
skipRevalidate: true,
|
||||
prevalidSizeOnly: true,
|
||||
checksumValidatorResolver: (checksum) => ({
|
||||
validate: async () => {
|
||||
/* void */
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
await depsTask.startAndWait({
|
||||
onStart(task) {
|
||||
event.sender.send('installation-status', {
|
||||
step: `dependencies.${task.path}`,
|
||||
message: `Начало: ${task.name || task.path}`,
|
||||
});
|
||||
},
|
||||
onUpdate(task, chunkSize) {
|
||||
const percentage =
|
||||
Math.round((depsTask.progress / depsTask.total) * 100) || 0;
|
||||
|
||||
event.sender.send('download-progress', percentage);
|
||||
event.sender.send('installation-status', {
|
||||
step: `dependencies.${task.path}`,
|
||||
message: `Установка ${task.name || task.path}: ${percentage}%`,
|
||||
});
|
||||
},
|
||||
onFailed(task, error) {
|
||||
event.sender.send('installation-status', {
|
||||
step: `dependencies.${task.path}`,
|
||||
message: `Ошибка: ${error.message}`,
|
||||
});
|
||||
},
|
||||
onSucceed(task, result) {
|
||||
event.sender.send('installation-status', {
|
||||
step: `dependencies.${task.path}`,
|
||||
message: `Завершено: ${task.name || task.path}`,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 5. Запускаем Minecraft
|
||||
event.sender.send('installation-status', {
|
||||
step: 'launch',
|
||||
message: 'Запуск игры...',
|
||||
});
|
||||
const comfortDir = path.join(versionsDir, 'Comfort');
|
||||
const proc = await launch({
|
||||
// Используем папку Comfort для всех сохранений/модов/конфигов
|
||||
gamePath: comfortDir,
|
||||
// Указываем путь к библиотекам и ассетам
|
||||
resourcePath: minecraftDir,
|
||||
javaPath,
|
||||
version: '1.21.4-fabric0.16.14',
|
||||
extraJVMArgs: ['-Dlog4j2.formatMsgNoLookups=true'],
|
||||
});
|
||||
|
||||
// Логирование
|
||||
proc.stdout?.on('data', (data) => {
|
||||
console.log(`Minecraft stdout: ${data}`);
|
||||
});
|
||||
proc.stderr?.on('data', (data) => {
|
||||
console.error(`Minecraft stderr: ${data}`);
|
||||
});
|
||||
|
||||
return { success: true, pid: proc.pid };
|
||||
} catch (error) {
|
||||
console.error('Ошибка при запуске Minecraft:', error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
const installExtensions = async () => {
|
||||
const installer = require('electron-devtools-installer');
|
||||
const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
|
||||
|
@ -1,8 +1,10 @@
|
||||
// Disable no-unused-vars, broken for spread args
|
||||
/* eslint no-unused-vars: off */
|
||||
import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron';
|
||||
|
||||
export type Channels = 'ipc-example';
|
||||
export type Channels =
|
||||
| 'ipc-example'
|
||||
| 'download-progress'
|
||||
| 'launch-minecraft'
|
||||
| 'installation-status';
|
||||
|
||||
const electronHandler = {
|
||||
ipcRenderer: {
|
||||
@ -21,6 +23,9 @@ const electronHandler = {
|
||||
once(channel: Channels, func: (...args: unknown[]) => void) {
|
||||
ipcRenderer.once(channel, (_event, ...args) => func(...args));
|
||||
},
|
||||
invoke(channel: string, ...args: unknown[]) {
|
||||
return ipcRenderer.invoke(channel, ...args);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
Navigate,
|
||||
} from 'react-router-dom';
|
||||
import Login from './pages/Login';
|
||||
import Dashboard from './pages/Dashboard';
|
||||
import LaunchPage from './pages/LaunchPage';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import './App.css';
|
||||
|
||||
@ -66,7 +66,7 @@ const App = () => {
|
||||
path="/"
|
||||
element={
|
||||
<AuthCheck>
|
||||
<Dashboard />
|
||||
<LaunchPage />
|
||||
</AuthCheck>
|
||||
}
|
||||
/>
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { Box, Typography, Button } from '@mui/material';
|
||||
import { useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
const Dashboard = () => {
|
||||
const navigate = useNavigate();
|
||||
|
||||
useEffect(() => {
|
||||
// Проверяем авторизацию при монтировании компонента
|
||||
const savedConfig = localStorage.getItem('launcher_config');
|
||||
if (!savedConfig || !JSON.parse(savedConfig).accessToken) {
|
||||
navigate('/login');
|
||||
}
|
||||
}, [navigate]);
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('launcher_config');
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h4">Добро пожаловать в лаунчер</Typography>
|
||||
<Button onClick={handleLogout} variant="contained" color="error">
|
||||
Выйти
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
166
src/renderer/pages/LaunchPage.tsx
Normal file
166
src/renderer/pages/LaunchPage.tsx
Normal file
@ -0,0 +1,166 @@
|
||||
import { Box, Typography, Button, Snackbar, Alert } from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
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;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const LaunchPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||
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('');
|
||||
|
||||
useEffect(() => {
|
||||
const savedConfig = localStorage.getItem('launcher_config');
|
||||
if (!savedConfig || !JSON.parse(savedConfig).accessToken) {
|
||||
navigate('/login');
|
||||
}
|
||||
|
||||
window.electron.ipcRenderer.on('download-progress', (progress: any) => {
|
||||
setDownloadProgress(progress as number);
|
||||
});
|
||||
|
||||
// Добавляем слушатель для статуса установки
|
||||
window.electron.ipcRenderer.on('installation-progress', (data: any) => {
|
||||
setInstallStatus((data as { status: string }).status);
|
||||
});
|
||||
|
||||
// Обновляем слушатель для статуса установки
|
||||
window.electron.ipcRenderer.on('installation-status', (data: any) => {
|
||||
const { step, message } = data as { step: string; message: string };
|
||||
setInstallStep(step);
|
||||
setInstallMessage(message);
|
||||
});
|
||||
|
||||
return () => {
|
||||
window.electron.ipcRenderer.removeAllListeners('download-progress');
|
||||
window.electron.ipcRenderer.removeAllListeners('installation-progress');
|
||||
window.electron.ipcRenderer.removeAllListeners('installation-status');
|
||||
};
|
||||
}, [navigate]);
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem('launcher_config');
|
||||
navigate('/login');
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// Сначала проверяем и обновляем файлы
|
||||
const downloadResult = await window.electron.ipcRenderer.invoke(
|
||||
'download-and-extract',
|
||||
);
|
||||
|
||||
if (downloadResult?.success) {
|
||||
if (downloadResult.updated) {
|
||||
showNotification(
|
||||
`Сборка успешно обновлена до версии ${downloadResult.version}`,
|
||||
'success',
|
||||
);
|
||||
} else {
|
||||
showNotification(
|
||||
`Установлена актуальная версия сборки ${downloadResult.version}`,
|
||||
'info',
|
||||
);
|
||||
}
|
||||
|
||||
// Затем запускаем Minecraft
|
||||
const launchResult =
|
||||
await window.electron.ipcRenderer.invoke('launch-minecraft');
|
||||
|
||||
if (launchResult?.success) {
|
||||
showNotification('Minecraft успешно запущен!', 'success');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
showNotification(`Ошибка: ${error.message}`, 'error');
|
||||
} finally {
|
||||
setIsDownloading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 3 }}>
|
||||
<Typography variant="h4" sx={{ mb: 3 }}>
|
||||
Добро пожаловать в лаунчер
|
||||
</Typography>
|
||||
|
||||
{isDownloading ? (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
<Typography>Загрузка и установка: {downloadProgress}%</Typography>
|
||||
{installMessage && (
|
||||
<Typography variant="body1" sx={{ mt: 1, color: 'white' }}>
|
||||
{installMessage}
|
||||
</Typography>
|
||||
)}
|
||||
{installStep && (
|
||||
<Typography variant="body2" sx={{ color: 'white' }}>
|
||||
Шаг: {installStep}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
sx={{ mb: 3 }}
|
||||
onClick={handleLaunchMinecraft}
|
||||
>
|
||||
Запустить Minecraft
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button onClick={handleLogout} variant="contained" color="error">
|
||||
Выйти
|
||||
</Button>
|
||||
|
||||
<Snackbar
|
||||
open={notification.open}
|
||||
autoHideDuration={6000}
|
||||
onClose={handleCloseNotification}
|
||||
>
|
||||
<Alert
|
||||
onClose={handleCloseNotification}
|
||||
severity={notification.severity}
|
||||
sx={{ width: '100%' }}
|
||||
>
|
||||
{notification.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default LaunchPage;
|
Reference in New Issue
Block a user