add normal progressbar and function to stop game

This commit is contained in:
2025-12-05 00:22:43 +05:00
parent e8ec4052ba
commit 734ca4fce5
3 changed files with 173 additions and 25 deletions

View File

@ -42,6 +42,36 @@ import { API_BASE_URL } from '../renderer/api';
// },
// };
const INSTALL_PHASES = [
{ id: 'download', weight: 0.25 }, // 25% — скачивание сборки
{ id: 'minecraft-install', weight: 0.3 }, // 30% — ваниль
{ id: 'fabric-install', weight: 0.15 }, // 15% — Fabric
{ id: 'dependencies', weight: 0.25 }, // 25% — библиотеки/ресурсы
{ id: 'launch', weight: 0.05 }, // 5% — запуск
] as const;
type InstallPhaseId = (typeof INSTALL_PHASES)[number]['id'];
function getGlobalProgress(phaseId: InstallPhaseId, localProgress01: number) {
let offset = 0;
let weight = 0;
for (const phase of INSTALL_PHASES) {
if (phase.id === phaseId) {
weight = phase.weight;
break;
}
offset += phase.weight;
}
if (!weight) return 100;
const clampedLocal = Math.max(0, Math.min(1, localProgress01));
const global = (offset + clampedLocal * weight) * 100;
return Math.round(Math.max(0, Math.min(global, 100)));
}
// Константы
const AUTHLIB_INJECTOR_FILENAME = 'authlib-injector-1.2.5.jar';
const MCSTATUS_API_URL = 'https://api.mcstatus.io/v2/status/java/';
@ -54,6 +84,8 @@ const agent = new Agent({
// тут можно задать и другие параметры при необходимости
});
let currentMinecraftProcess: any | null = null;
// Модифицированная функция для получения последней версии релиза с произвольного URL
export async function getLatestReleaseVersion(apiUrl: string): Promise<string> {
try {
@ -419,6 +451,9 @@ export function initMinecraftHandlers() {
// Скачиваем файл
await downloadFile(downloadUrl, zipPath, (progress) => {
event.sender.send('download-progress', progress);
const global = getGlobalProgress('download', progress / 100);
event.sender.send('overall-progress', global);
});
// Проверяем архив
@ -617,7 +652,6 @@ export function initMinecraftHandlers() {
assetsDownloadConcurrency: 2,
librariesDownloadConcurrency: 2,
dispatcher: agent,
// ...DOWNLOAD_OPTIONS,
});
console.log('installMcTask started for', minecraftVersion.id);
@ -628,6 +662,16 @@ export function initMinecraftHandlers() {
});
await installMcTask.startAndWait({
onUpdate(task, chunkSize) {
// локальный прогресс инсталлятора XMCL
const local =
installMcTask.total > 0
? installMcTask.progress / installMcTask.total
: 0;
const global = getGlobalProgress('minecraft-install', local);
event.sender.send('overall-progress', global);
},
onFailed(task, error) {
const stepName = (task as any).path || task.name || 'unknown';
console.warn(
@ -666,6 +710,11 @@ export function initMinecraftHandlers() {
message: `Установка Fabric ${fabricVersion}...`,
});
event.sender.send(
'overall-progress',
getGlobalProgress('fabric-install', 0),
);
console.log('installFabric:', {
minecraftVersion: effectiveBaseVersion,
fabricVersion,
@ -677,6 +726,10 @@ export function initMinecraftHandlers() {
version: fabricVersion,
minecraft: minecraftDir,
});
event.sender.send(
'overall-progress',
getGlobalProgress('fabric-install', 1),
);
} catch (error) {
console.log('Ошибка при установке Fabric, продолжаем:', error);
}
@ -720,6 +773,13 @@ export function initMinecraftHandlers() {
});
await depsTask.startAndWait({
onUpdate(task, chunkSize) {
const local =
depsTask.total > 0 ? depsTask.progress / depsTask.total : 0;
const global = getGlobalProgress('dependencies', local);
event.sender.send('overall-progress', global);
},
onFailed(task, error) {
const stepName = (task as any).path || task.name || 'unknown';
console.warn(
@ -758,6 +818,8 @@ export function initMinecraftHandlers() {
message: 'Запуск игры...',
});
event.sender.send('overall-progress', getGlobalProgress('launch', 0));
const proc = await launch({
gamePath: packDir,
resourcePath: minecraftDir,
@ -785,6 +847,12 @@ export function initMinecraftHandlers() {
},
});
event.sender.send('minecraft-started', { pid: proc.pid });
currentMinecraftProcess = proc;
event.sender.send('overall-progress', getGlobalProgress('launch', 1));
let stderrBuffer = '';
proc.stdout?.on('data', (data) => {
@ -810,6 +878,9 @@ export function initMinecraftHandlers() {
proc.on('exit', (code) => {
console.log('Minecraft exited with code', code);
currentMinecraftProcess = null;
event.sender.send('minecraft-stopped', { code });
if (code !== 0) {
event.sender.send('installation-status', {
step: 'error',
@ -838,6 +909,24 @@ export function initMinecraftHandlers() {
}
});
ipcMain.handle('stop-minecraft', async (event) => {
try {
if (currentMinecraftProcess && !currentMinecraftProcess.killed) {
console.log('Останавливаем Minecraft по запросу пользователя...');
// На Windows этого обычно достаточно
currentMinecraftProcess.kill();
// Можно чуть подождать, но не обязательно
return { success: true };
}
return { success: false, error: 'Minecraft сейчас не запущен' };
} catch (error: any) {
console.error('Ошибка при остановке Minecraft:', error);
return { success: false, error: error.message || String(error) };
}
});
// Добавьте в функцию initMinecraftHandlers или создайте новую
ipcMain.handle('get-pack-files', async (event, packName) => {
try {

View File

@ -15,7 +15,11 @@ export type Channels =
| 'get-installed-versions'
| 'get-available-versions'
| 'minecraft-log'
| 'minecraft-error';
| 'minecraft-error'
| 'overall-progress'
| 'stop-minecraft'
| 'minecraft-started'
| 'minecraft-stopped';
const electronHandler = {
ipcRenderer: {

View File

@ -59,7 +59,7 @@ const LaunchPage = ({
preserveFiles: [],
});
const [isDownloading, setIsDownloading] = useState(false);
const [downloadProgress, setDownloadProgress] = useState(0);
const [progress, setProgress] = useState(0);
const [buffer, setBuffer] = useState(10);
const [installStatus, setInstallStatus] = useState('');
const [notification, setNotification] = useState<{
@ -70,6 +70,7 @@ const LaunchPage = ({
const [installStep, setInstallStep] = useState('');
const [installMessage, setInstallMessage] = useState('');
const [open, setOpen] = React.useState(false);
const [isGameRunning, setIsGameRunning] = useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
@ -79,10 +80,10 @@ const LaunchPage = ({
navigate('/login');
}
const progressListener = (...args: unknown[]) => {
const progress = args[0] as number;
setDownloadProgress(progress);
setBuffer(Math.min(progress + 10, 100));
const overallProgressListener = (...args: unknown[]) => {
const value = args[0] as number; // 0..100
setProgress(value);
setBuffer(Math.min(value + 10, 100));
};
const statusListener = (...args: unknown[]) => {
@ -106,18 +107,34 @@ const LaunchPage = ({
);
};
const minecraftStartedListener = () => {
setIsGameRunning(true);
};
const minecraftStoppedListener = () => {
setIsGameRunning(false);
};
window.electron.ipcRenderer.on('overall-progress', overallProgressListener);
window.electron.ipcRenderer.on('minecraft-error', minecraftErrorListener);
window.electron.ipcRenderer.on('download-progress', progressListener);
window.electron.ipcRenderer.on('installation-status', statusListener);
window.electron.ipcRenderer.on(
'minecraft-started',
minecraftStartedListener,
);
window.electron.ipcRenderer.on(
'minecraft-stopped',
minecraftStoppedListener,
);
return () => {
// Удаляем только конкретных слушателей, а не всех
// Это безопаснее, чем removeAllListeners
const cleanup = window.electron.ipcRenderer.on;
if (typeof cleanup === 'function') {
cleanup('download-progress', progressListener);
cleanup('installation-status', statusListener);
cleanup('minecraft-error', statusListener);
cleanup('overall-progress', overallProgressListener);
}
// Удаляем использование removeAllListeners
};
@ -220,7 +237,6 @@ const LaunchPage = ({
const handleLaunchMinecraft = async () => {
try {
setIsDownloading(true);
setDownloadProgress(0);
setBuffer(10);
// Используем настройки выбранной версии или дефолтные
@ -305,6 +321,28 @@ const LaunchPage = ({
}
};
const handleStopMinecraft = async () => {
try {
const result = await window.electron.ipcRenderer.invoke('stop-minecraft');
if (result?.success) {
showNotification('Minecraft остановлен', 'info');
setIsGameRunning(false);
} else if (result?.error) {
showNotification(
`Не удалось остановить Minecraft: ${result.error}`,
'error',
);
}
} catch (error: any) {
console.error('Ошибка при остановке Minecraft:', error);
showNotification(
`Ошибка при остановке Minecraft: ${error.message || String(error)}`,
'error',
);
}
};
// Функция для сохранения настроек
const savePackConfig = async () => {
try {
@ -370,7 +408,7 @@ const LaunchPage = ({
<Box sx={{ width: '100%', mr: 1 }}>
<LinearProgress
variant="buffer"
value={downloadProgress}
value={progress}
valueBuffer={buffer}
/>
</Box>
@ -378,7 +416,7 @@ const LaunchPage = ({
<Typography
variant="body2"
sx={{ color: 'white' }}
>{`${Math.round(downloadProgress)}%`}</Typography>
>{`${Math.round(progress)}%`}</Typography>
</Box>
</Box>
</Box>
@ -394,25 +432,42 @@ const LaunchPage = ({
<Button
variant="contained"
color="primary"
onClick={handleLaunchMinecraft}
onClick={
isGameRunning ? handleStopMinecraft : handleLaunchMinecraft
}
sx={{
flexGrow: 1, // занимает всё свободное место
width: 'auto', // ширина подстраивается
flexGrow: 1,
width: 'auto',
borderRadius: '3vw',
fontFamily: 'Benzin-Bold',
background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
transition: 'transform 0.3s ease',
'&:hover': {
background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
transform: 'scale(1.05)',
boxShadow: '0 4px 15px rgba(242, 113, 33, 0.4)',
},
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
...(isGameRunning
? {
// 🔹 Стиль, когда игра запущена (серая кнопка)
background: 'linear-gradient(71deg, #555 0%, #777 100%)',
'&:hover': {
background: 'linear-gradient(71deg, #666 0%, #888 100%)',
transform: 'scale(1.05)',
boxShadow: '0 4px 15px rgba(100, 100, 100, 0.4)',
},
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
}
: {
// 🔹 Стиль, когда Minecraft НЕ запущен (твоя стандартная красочная кнопка)
background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
'&:hover': {
background:
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
transform: 'scale(1.05)',
boxShadow: '0 4px 15px rgba(242, 113, 33, 0.4)',
},
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
}),
}}
>
Запустить Minecraft
{isGameRunning ? 'Остановить Minecraft' : 'Запустить Minecraft'}
</Button>
{/* Вторая кнопка — квадратная, фиксированного размера (ширина = высоте) */}