add normal progressbar and function to stop game
This commit is contained in:
@ -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 {
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -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>
|
||||
|
||||
{/* Вторая кнопка — квадратная, фиксированного размера (ширина = высоте) */}
|
||||
|
||||
Reference in New Issue
Block a user