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 AUTHLIB_INJECTOR_FILENAME = 'authlib-injector-1.2.5.jar';
|
||||||
const MCSTATUS_API_URL = 'https://api.mcstatus.io/v2/status/java/';
|
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
|
// Модифицированная функция для получения последней версии релиза с произвольного URL
|
||||||
export async function getLatestReleaseVersion(apiUrl: string): Promise<string> {
|
export async function getLatestReleaseVersion(apiUrl: string): Promise<string> {
|
||||||
try {
|
try {
|
||||||
@ -419,6 +451,9 @@ export function initMinecraftHandlers() {
|
|||||||
// Скачиваем файл
|
// Скачиваем файл
|
||||||
await downloadFile(downloadUrl, zipPath, (progress) => {
|
await downloadFile(downloadUrl, zipPath, (progress) => {
|
||||||
event.sender.send('download-progress', 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,
|
assetsDownloadConcurrency: 2,
|
||||||
librariesDownloadConcurrency: 2,
|
librariesDownloadConcurrency: 2,
|
||||||
dispatcher: agent,
|
dispatcher: agent,
|
||||||
// ...DOWNLOAD_OPTIONS,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('installMcTask started for', minecraftVersion.id);
|
console.log('installMcTask started for', minecraftVersion.id);
|
||||||
@ -628,6 +662,16 @@ export function initMinecraftHandlers() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await installMcTask.startAndWait({
|
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) {
|
onFailed(task, error) {
|
||||||
const stepName = (task as any).path || task.name || 'unknown';
|
const stepName = (task as any).path || task.name || 'unknown';
|
||||||
console.warn(
|
console.warn(
|
||||||
@ -666,6 +710,11 @@ export function initMinecraftHandlers() {
|
|||||||
message: `Установка Fabric ${fabricVersion}...`,
|
message: `Установка Fabric ${fabricVersion}...`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
event.sender.send(
|
||||||
|
'overall-progress',
|
||||||
|
getGlobalProgress('fabric-install', 0),
|
||||||
|
);
|
||||||
|
|
||||||
console.log('installFabric:', {
|
console.log('installFabric:', {
|
||||||
minecraftVersion: effectiveBaseVersion,
|
minecraftVersion: effectiveBaseVersion,
|
||||||
fabricVersion,
|
fabricVersion,
|
||||||
@ -677,6 +726,10 @@ export function initMinecraftHandlers() {
|
|||||||
version: fabricVersion,
|
version: fabricVersion,
|
||||||
minecraft: minecraftDir,
|
minecraft: minecraftDir,
|
||||||
});
|
});
|
||||||
|
event.sender.send(
|
||||||
|
'overall-progress',
|
||||||
|
getGlobalProgress('fabric-install', 1),
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Ошибка при установке Fabric, продолжаем:', error);
|
console.log('Ошибка при установке Fabric, продолжаем:', error);
|
||||||
}
|
}
|
||||||
@ -720,6 +773,13 @@ export function initMinecraftHandlers() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await depsTask.startAndWait({
|
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) {
|
onFailed(task, error) {
|
||||||
const stepName = (task as any).path || task.name || 'unknown';
|
const stepName = (task as any).path || task.name || 'unknown';
|
||||||
console.warn(
|
console.warn(
|
||||||
@ -758,6 +818,8 @@ export function initMinecraftHandlers() {
|
|||||||
message: 'Запуск игры...',
|
message: 'Запуск игры...',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
event.sender.send('overall-progress', getGlobalProgress('launch', 0));
|
||||||
|
|
||||||
const proc = await launch({
|
const proc = await launch({
|
||||||
gamePath: packDir,
|
gamePath: packDir,
|
||||||
resourcePath: minecraftDir,
|
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 = '';
|
let stderrBuffer = '';
|
||||||
|
|
||||||
proc.stdout?.on('data', (data) => {
|
proc.stdout?.on('data', (data) => {
|
||||||
@ -810,6 +878,9 @@ export function initMinecraftHandlers() {
|
|||||||
proc.on('exit', (code) => {
|
proc.on('exit', (code) => {
|
||||||
console.log('Minecraft exited with code', code);
|
console.log('Minecraft exited with code', code);
|
||||||
|
|
||||||
|
currentMinecraftProcess = null;
|
||||||
|
event.sender.send('minecraft-stopped', { code });
|
||||||
|
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
event.sender.send('installation-status', {
|
event.sender.send('installation-status', {
|
||||||
step: 'error',
|
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 или создайте новую
|
// Добавьте в функцию initMinecraftHandlers или создайте новую
|
||||||
ipcMain.handle('get-pack-files', async (event, packName) => {
|
ipcMain.handle('get-pack-files', async (event, packName) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -15,7 +15,11 @@ export type Channels =
|
|||||||
| 'get-installed-versions'
|
| 'get-installed-versions'
|
||||||
| 'get-available-versions'
|
| 'get-available-versions'
|
||||||
| 'minecraft-log'
|
| 'minecraft-log'
|
||||||
| 'minecraft-error';
|
| 'minecraft-error'
|
||||||
|
| 'overall-progress'
|
||||||
|
| 'stop-minecraft'
|
||||||
|
| 'minecraft-started'
|
||||||
|
| 'minecraft-stopped';
|
||||||
|
|
||||||
const electronHandler = {
|
const electronHandler = {
|
||||||
ipcRenderer: {
|
ipcRenderer: {
|
||||||
|
|||||||
@ -59,7 +59,7 @@ const LaunchPage = ({
|
|||||||
preserveFiles: [],
|
preserveFiles: [],
|
||||||
});
|
});
|
||||||
const [isDownloading, setIsDownloading] = useState(false);
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
const [progress, setProgress] = useState(0);
|
||||||
const [buffer, setBuffer] = useState(10);
|
const [buffer, setBuffer] = useState(10);
|
||||||
const [installStatus, setInstallStatus] = useState('');
|
const [installStatus, setInstallStatus] = useState('');
|
||||||
const [notification, setNotification] = useState<{
|
const [notification, setNotification] = useState<{
|
||||||
@ -70,6 +70,7 @@ const LaunchPage = ({
|
|||||||
const [installStep, setInstallStep] = useState('');
|
const [installStep, setInstallStep] = useState('');
|
||||||
const [installMessage, setInstallMessage] = useState('');
|
const [installMessage, setInstallMessage] = useState('');
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
|
const [isGameRunning, setIsGameRunning] = useState(false);
|
||||||
const handleOpen = () => setOpen(true);
|
const handleOpen = () => setOpen(true);
|
||||||
const handleClose = () => setOpen(false);
|
const handleClose = () => setOpen(false);
|
||||||
|
|
||||||
@ -79,10 +80,10 @@ const LaunchPage = ({
|
|||||||
navigate('/login');
|
navigate('/login');
|
||||||
}
|
}
|
||||||
|
|
||||||
const progressListener = (...args: unknown[]) => {
|
const overallProgressListener = (...args: unknown[]) => {
|
||||||
const progress = args[0] as number;
|
const value = args[0] as number; // 0..100
|
||||||
setDownloadProgress(progress);
|
setProgress(value);
|
||||||
setBuffer(Math.min(progress + 10, 100));
|
setBuffer(Math.min(value + 10, 100));
|
||||||
};
|
};
|
||||||
|
|
||||||
const statusListener = (...args: unknown[]) => {
|
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('minecraft-error', minecraftErrorListener);
|
||||||
window.electron.ipcRenderer.on('download-progress', progressListener);
|
|
||||||
window.electron.ipcRenderer.on('installation-status', statusListener);
|
window.electron.ipcRenderer.on('installation-status', statusListener);
|
||||||
|
window.electron.ipcRenderer.on(
|
||||||
|
'minecraft-started',
|
||||||
|
minecraftStartedListener,
|
||||||
|
);
|
||||||
|
window.electron.ipcRenderer.on(
|
||||||
|
'minecraft-stopped',
|
||||||
|
minecraftStoppedListener,
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// Удаляем только конкретных слушателей, а не всех
|
// Удаляем только конкретных слушателей, а не всех
|
||||||
// Это безопаснее, чем removeAllListeners
|
// Это безопаснее, чем removeAllListeners
|
||||||
const cleanup = window.electron.ipcRenderer.on;
|
const cleanup = window.electron.ipcRenderer.on;
|
||||||
if (typeof cleanup === 'function') {
|
if (typeof cleanup === 'function') {
|
||||||
cleanup('download-progress', progressListener);
|
|
||||||
cleanup('installation-status', statusListener);
|
cleanup('installation-status', statusListener);
|
||||||
cleanup('minecraft-error', statusListener);
|
cleanup('minecraft-error', statusListener);
|
||||||
|
cleanup('overall-progress', overallProgressListener);
|
||||||
}
|
}
|
||||||
// Удаляем использование removeAllListeners
|
// Удаляем использование removeAllListeners
|
||||||
};
|
};
|
||||||
@ -220,7 +237,6 @@ const LaunchPage = ({
|
|||||||
const handleLaunchMinecraft = async () => {
|
const handleLaunchMinecraft = async () => {
|
||||||
try {
|
try {
|
||||||
setIsDownloading(true);
|
setIsDownloading(true);
|
||||||
setDownloadProgress(0);
|
|
||||||
setBuffer(10);
|
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 () => {
|
const savePackConfig = async () => {
|
||||||
try {
|
try {
|
||||||
@ -370,7 +408,7 @@ const LaunchPage = ({
|
|||||||
<Box sx={{ width: '100%', mr: 1 }}>
|
<Box sx={{ width: '100%', mr: 1 }}>
|
||||||
<LinearProgress
|
<LinearProgress
|
||||||
variant="buffer"
|
variant="buffer"
|
||||||
value={downloadProgress}
|
value={progress}
|
||||||
valueBuffer={buffer}
|
valueBuffer={buffer}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -378,7 +416,7 @@ const LaunchPage = ({
|
|||||||
<Typography
|
<Typography
|
||||||
variant="body2"
|
variant="body2"
|
||||||
sx={{ color: 'white' }}
|
sx={{ color: 'white' }}
|
||||||
>{`${Math.round(downloadProgress)}%`}</Typography>
|
>{`${Math.round(progress)}%`}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -394,25 +432,42 @@ const LaunchPage = ({
|
|||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={handleLaunchMinecraft}
|
onClick={
|
||||||
|
isGameRunning ? handleStopMinecraft : handleLaunchMinecraft
|
||||||
|
}
|
||||||
sx={{
|
sx={{
|
||||||
flexGrow: 1, // занимает всё свободное место
|
flexGrow: 1,
|
||||||
width: 'auto', // ширина подстраивается
|
width: 'auto',
|
||||||
borderRadius: '3vw',
|
borderRadius: '3vw',
|
||||||
fontFamily: 'Benzin-Bold',
|
fontFamily: 'Benzin-Bold',
|
||||||
background:
|
|
||||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
|
||||||
transition: 'transform 0.3s ease',
|
transition: 'transform 0.3s ease',
|
||||||
'&:hover': {
|
|
||||||
background:
|
...(isGameRunning
|
||||||
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
? {
|
||||||
transform: 'scale(1.05)',
|
// 🔹 Стиль, когда игра запущена (серая кнопка)
|
||||||
boxShadow: '0 4px 15px rgba(242, 113, 33, 0.4)',
|
background: 'linear-gradient(71deg, #555 0%, #777 100%)',
|
||||||
},
|
'&:hover': {
|
||||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
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>
|
</Button>
|
||||||
|
|
||||||
{/* Вторая кнопка — квадратная, фиксированного размера (ширина = высоте) */}
|
{/* Вторая кнопка — квадратная, фиксированного размера (ширина = высоте) */}
|
||||||
|
|||||||
Reference in New Issue
Block a user