diff --git a/src/main/main.ts b/src/main/main.ts
index 5b23377..a724e67 100644
--- a/src/main/main.ts
+++ b/src/main/main.ts
@@ -119,7 +119,7 @@ const createWindow = async () => {
width: 1024,
height: 850,
autoHideMenuBar: true,
- resizable: false,
+ resizable: true,
frame: false,
icon: getAssetPath('icon.png'),
webPreferences: {
diff --git a/src/main/minecraft-launcher.ts b/src/main/minecraft-launcher.ts
index 50a55eb..dfe7c4e 100644
--- a/src/main/minecraft-launcher.ts
+++ b/src/main/minecraft-launcher.ts
@@ -805,8 +805,11 @@ export function initMinecraftHandlers() {
}
});
- // Добавьте в функцию initMinecraftHandlers новый обработчик
- ipcMain.handle('get-available-versions', async (event) => {
+ // ПРОБЛЕМА: У вас два обработчика для одного и того же канала 'get-installed-versions'
+ // РЕШЕНИЕ: Объединим логику в один обработчик, а из второго обработчика вызовем функцию getInstalledVersions
+
+ // Сначала создаем общую функцию для получения установленных версий
+ function getInstalledVersions() {
try {
const appPath = path.dirname(app.getPath('exe'));
const minecraftDir = path.join(appPath, '.minecraft');
@@ -846,9 +849,59 @@ export function initMinecraftHandlers() {
}
return { success: true, versions };
+ } catch (error) {
+ console.error('Ошибка при получении установленных версий:', error);
+ return { success: false, error: error.message, versions: [] };
+ }
+ }
+
+ // Регистрируем обработчик для get-installed-versions
+ ipcMain.handle('get-installed-versions', async () => {
+ return getInstalledVersions();
+ });
+
+ // Обработчик get-available-versions использует функцию getInstalledVersions
+ ipcMain.handle('get-available-versions', async (event, { gistUrl }) => {
+ try {
+ // Используем URL из параметров или значение по умолчанию
+ const url =
+ gistUrl ||
+ 'https://gist.githubusercontent.com/DIKER0K/06cd12fb3a4d08b1f0f8c763a7d05e06/raw/versions.json';
+
+ const response = await fetch(url);
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch versions from Gist: ${response.status} ${response.statusText}`,
+ );
+ }
+
+ const versions = await response.json();
+
+ // Получаем уже установленные версии
+ const installedResult = getInstalledVersions();
+ const installedVersions = installedResult.success
+ ? installedResult.versions
+ : [];
+
+ // Добавляем флаг installed к каждой версии
+ const versionsWithInstallStatus = versions.map((version: any) => {
+ const isInstalled = installedVersions.some(
+ (installed: any) => installed.id === version.id,
+ );
+ return {
+ ...version,
+ installed: isInstalled,
+ };
+ });
+
+ return {
+ success: true,
+ versions: versionsWithInstallStatus,
+ };
} catch (error) {
console.error('Ошибка при получении доступных версий:', error);
- return { success: false, error: error.message };
+ return { success: false, error: error.message, versions: [] };
}
});
}
diff --git a/src/main/preload.ts b/src/main/preload.ts
index db18036..c6542dd 100644
--- a/src/main/preload.ts
+++ b/src/main/preload.ts
@@ -11,7 +11,9 @@ export type Channels =
| 'save-pack-config'
| 'load-pack-config'
| 'update-available'
- | 'install-update';
+ | 'install-update'
+ | 'get-installed-versions'
+ | 'get-available-versions';
const electronHandler = {
ipcRenderer: {
diff --git a/src/renderer/App.css b/src/renderer/App.css
index acfd42a..f888bad 100644
--- a/src/renderer/App.css
+++ b/src/renderer/App.css
@@ -47,3 +47,7 @@ h4 {
h5 {
font-family: 'Benzin-Bold' !important;
}
+
+h6 {
+ font-family: 'Benzin-Bold' !important;
+}
diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx
index c2f5f7f..3180893 100644
--- a/src/renderer/App.tsx
+++ b/src/renderer/App.tsx
@@ -43,15 +43,37 @@ const AuthCheck = ({ children }: { children: ReactNode }) => {
const validateToken = async (token: string) => {
try {
- const response = await fetch('https://authserver.ely.by/auth/validate', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ accessToken: token }),
- });
- return response.ok;
+ // Используем IPC для валидации токена через main процесс
+ const result = await window.electron.ipcRenderer.invoke(
+ 'validate-token',
+ token,
+ );
+
+ // Если токен недействителен, очищаем сохраненные данные в localStorage
+ if (!result.valid) {
+ console.log(
+ 'Токен недействителен, очищаем данные авторизации из localStorage',
+ );
+ const savedConfig = localStorage.getItem('launcher_config');
+ if (savedConfig) {
+ const config = JSON.parse(savedConfig);
+ // Сохраняем только логин и другие настройки, но удаляем токены
+ const cleanedConfig = {
+ username: config.username,
+ memory: config.memory || 4096,
+ comfortVersion: config.comfortVersion || '',
+ password: '', // Очищаем пароль для безопасности
+ };
+ localStorage.setItem(
+ 'launcher_config',
+ JSON.stringify(cleanedConfig),
+ );
+ }
+ }
+
+ return result.valid;
} catch (error) {
+ console.error('Ошибка проверки токена:', error);
return false;
}
};
@@ -80,6 +102,7 @@ const App = () => {
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
+ overflowX: 'hidden',
}}
>
diff --git a/src/renderer/components/Settings/SettingsModal.tsx b/src/renderer/components/Settings/SettingsModal.tsx
new file mode 100644
index 0000000..71264b2
--- /dev/null
+++ b/src/renderer/components/Settings/SettingsModal.tsx
@@ -0,0 +1,92 @@
+import { Box, Typography, Button, Modal } from '@mui/material';
+import React from 'react';
+import MemorySlider from '../Login/MemorySlider';
+import FilesSelector from '../FilesSelector';
+
+interface SettingsModalProps {
+ open: boolean;
+ onClose: () => void;
+ config: {
+ memory: number;
+ preserveFiles: string[];
+ };
+ onConfigChange: (newConfig: {
+ memory: number;
+ preserveFiles: string[];
+ }) => void;
+ packName: string;
+ onSave: () => void;
+}
+
+const SettingsModal = ({
+ open,
+ onClose,
+ config,
+ onConfigChange,
+ packName,
+ onSave,
+}: SettingsModalProps) => {
+ return (
+
+
+
+ Файлы и папки, которые будут сохранены после переустановки сборки
+
+ {
+ onConfigChange({ ...config, preserveFiles: selected });
+ }}
+ />
+
+ Оперативная память выделенная для Minecraft
+
+ {
+ onConfigChange({ ...config, memory: value as number });
+ }}
+ />
+
+
+
+ );
+};
+
+export default SettingsModal;
diff --git a/src/renderer/pages/LaunchPage.tsx b/src/renderer/pages/LaunchPage.tsx
index 6a20b6b..562d117 100644
--- a/src/renderer/pages/LaunchPage.tsx
+++ b/src/renderer/pages/LaunchPage.tsx
@@ -5,7 +5,6 @@ import {
Snackbar,
Alert,
LinearProgress,
- Modal,
} from '@mui/material';
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
@@ -13,8 +12,7 @@ import ServerStatus from '../components/ServerStatus/ServerStatus';
import PopaPopa from '../components/popa-popa';
import SettingsIcon from '@mui/icons-material/Settings';
import React from 'react';
-import MemorySlider from '../components/Login/MemorySlider';
-import FilesSelector from '../components/FilesSelector';
+import SettingsModal from '../components/Settings/SettingsModal';
declare global {
interface Window {
@@ -44,7 +42,10 @@ interface LaunchPageProps {
};
}
-const LaunchPage = ({ onLaunchPage, launchOptions = {} as any }: LaunchPageProps) => {
+const LaunchPage = ({
+ onLaunchPage,
+ launchOptions = {} as any,
+}: LaunchPageProps) => {
const navigate = useNavigate();
const { versionId } = useParams();
const [versionConfig, setVersionConfig] = useState(null);
@@ -417,65 +418,14 @@ const LaunchPage = ({ onLaunchPage, launchOptions = {} as any }: LaunchPageProps
-
-
-
- Файлы и папки, которые будут сохранены после переустановки сборки
-
- {
- setConfig((prev) => ({ ...prev, preserveFiles: selected }));
- }}
- />
-
- Оперативная память выделенная для Minecraft
-
- {
- setConfig((prev) => ({ ...prev, memory: value as number }));
- }}
- />
-
-
-
+ config={config}
+ onConfigChange={setConfig}
+ packName={versionId || versionConfig?.packName || 'Comfort'}
+ onSave={savePackConfig}
+ />
);
};
diff --git a/src/renderer/pages/Login.tsx b/src/renderer/pages/Login.tsx
index 1ed3337..3ac0586 100644
--- a/src/renderer/pages/Login.tsx
+++ b/src/renderer/pages/Login.tsx
@@ -38,13 +38,25 @@ const Login = () => {
console.log(
'Не удалось обновить токен, требуется новая авторизация',
);
- const newSession = await authenticateWithElyBy(
- config.username,
- config.password,
- saveConfig,
- );
- if (!newSession) {
- console.log('Авторизация не удалась');
+ // Очищаем недействительные токены
+ saveConfig({
+ accessToken: '',
+ clientToken: '',
+ });
+
+ // Пытаемся выполнить новую авторизацию
+ if (config.password) {
+ const newSession = await authenticateWithElyBy(
+ config.username,
+ config.password,
+ saveConfig,
+ );
+ if (!newSession) {
+ console.log('Авторизация не удалась');
+ return;
+ }
+ } else {
+ console.log('Требуется ввод пароля для новой авторизации');
return;
}
}
@@ -53,6 +65,13 @@ const Login = () => {
}
} else {
console.log('Токен отсутствует, выполняем авторизацию...');
+ // Проверяем наличие пароля
+ if (!config.password) {
+ console.log('Ошибка: не указан пароль');
+ alert('Введите пароль!');
+ return;
+ }
+
const session = await authenticateWithElyBy(
config.username,
config.password,
@@ -68,6 +87,11 @@ const Login = () => {
navigate('/');
} catch (error) {
console.log(`ОШИБКА при авторизации: ${error.message}`);
+ // Очищаем недействительные токены при ошибке
+ saveConfig({
+ accessToken: '',
+ clientToken: '',
+ });
}
};
diff --git a/src/renderer/pages/VersionsExplorer.tsx b/src/renderer/pages/VersionsExplorer.tsx
index 265fc1d..793627a 100644
--- a/src/renderer/pages/VersionsExplorer.tsx
+++ b/src/renderer/pages/VersionsExplorer.tsx
@@ -9,8 +9,15 @@ import {
CardActions,
Button,
CircularProgress,
+ Modal,
+ List,
+ ListItem,
+ ListItemText,
+ IconButton,
} from '@mui/material';
import { useNavigate } from 'react-router-dom';
+import AddIcon from '@mui/icons-material/Add';
+import DownloadIcon from '@mui/icons-material/Download';
interface VersionCardProps {
id: string;
@@ -32,51 +39,30 @@ const VersionCard: React.FC = ({
sx={{
backgroundColor: 'rgba(30, 30, 50, 0.8)',
backdropFilter: 'blur(10px)',
- width: '100%',
- height: '100%',
+ width: '35vw',
+ height: '35vh',
+ minWidth: 'unset',
+ minHeight: 'unset',
display: 'flex',
flexDirection: 'column',
borderRadius: '16px',
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
transition: 'transform 0.3s, box-shadow 0.3s',
overflow: 'hidden',
- '&:hover': {
- transform: 'translateY(-8px)',
- boxShadow: '0 12px 40px rgba(0, 0, 0, 0.5)',
- },
+ cursor: 'pointer',
}}
+ onClick={() => onSelect(id)}
>
-
-
-
-
- {version}
-
-
-
-
-
+
= ({
>
{name}
-
- Версия: {version}
-
-
-
-
-
);
};
@@ -142,40 +98,62 @@ interface VersionInfo {
};
}
+interface AvailableVersionInfo {
+ 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 [installedVersions, setInstalledVersions] = useState([]);
+ const [availableVersions, setAvailableVersions] = useState<
+ AvailableVersionInfo[]
+ >([]);
const [loading, setLoading] = useState(true);
+ const [modalOpen, setModalOpen] = useState(false);
+ const [downloadLoading, setDownloadLoading] = useState(null);
const navigate = useNavigate();
useEffect(() => {
const fetchVersions = async () => {
try {
- const result = await window.electron.ipcRenderer.invoke(
- 'get-available-versions',
+ setLoading(true);
+
+ // Получаем список установленных версий через IPC
+ const installedResult = await window.electron.ipcRenderer.invoke(
+ 'get-installed-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 },
- );
+ if (installedResult.success) {
+ setInstalledVersions(installedResult.versions);
+ }
- return {
- ...version,
- config: configResult.success ? configResult.config : undefined,
- };
- }),
- );
-
- setVersions(versionsWithConfig);
- } else {
- console.error('Ошибка получения версий:', result.error);
+ // Получаем доступные версии с GitHub Gist
+ const availableResult = await window.electron.ipcRenderer.invoke(
+ 'get-available-versions',
+ {
+ gistUrl:
+ 'https://gist.githubusercontent.com/DIKER0K/06cd12fb3a4d08b1f0f8c763a7d05e06/raw/versions.json',
+ },
+ );
+ if (availableResult.success) {
+ setAvailableVersions(availableResult.versions);
}
} catch (error) {
- console.error('Ошибка при запросе версий:', error);
+ console.error('Ошибка при загрузке версий:', error);
+ // Можно добавить обработку ошибки, например показать уведомление
} finally {
setLoading(false);
}
@@ -185,7 +163,6 @@ export const VersionsExplorer = () => {
}, []);
const handleSelectVersion = (version: VersionInfo) => {
- // Сохраняем конфигурацию в localStorage для использования в LaunchPage
localStorage.setItem(
'selected_version_config',
JSON.stringify(version.config || {}),
@@ -193,45 +170,94 @@ export const VersionsExplorer = () => {
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'],
- },
- },
- ];
+ const handleAddVersion = () => {
+ setModalOpen(true);
+ };
+
+ const handleCloseModal = () => {
+ setModalOpen(false);
+ };
+
+ const handleDownloadVersion = async (version: AvailableVersionInfo) => {
+ try {
+ setDownloadLoading(version.id);
+
+ // Скачивание и установка выбранной версии
+ const downloadResult = await window.electron.ipcRenderer.invoke(
+ 'download-and-extract',
+ {
+ downloadUrl: version.config.downloadUrl,
+ apiReleaseUrl: version.config.apiReleaseUrl,
+ versionFileName: version.config.versionFileName,
+ packName: version.id,
+ preserveFiles: version.config.preserveFiles || [],
+ },
+ );
+
+ if (downloadResult?.success) {
+ // Добавляем скачанную версию в список установленных
+ setInstalledVersions((prev) => [...prev, version]);
+ setModalOpen(false);
+ }
+ } catch (error) {
+ console.error(`Ошибка при скачивании версии ${version.id}:`, error);
+ } finally {
+ setDownloadLoading(null);
+ }
+ };
+
+ // Карточка добавления новой версии
+ const AddVersionCard = () => (
+
+
+
+ Добавить
+
+
+ версию
+
+
+ );
return (
-
- Доступные версии
-
-
{loading ? (
@@ -241,28 +267,128 @@ export const VersionsExplorer = () => {
container
spacing={3}
sx={{
- display: 'flex',
- flexWrap: 'wrap',
- justifyContent: 'center',
width: '100%',
+ overflowY: 'auto',
+ justifyContent: 'center',
}}
>
- {displayVersions.map((version) => (
-
- handleSelectVersion(version)}
- />
+ {/* Показываем установленные версии или дефолтную, если она есть */}
+ {installedVersions.length > 0 ? (
+ installedVersions.map((version) => (
+
+ handleSelectVersion(version)}
+ />
+
+ ))
+ ) : (
+ // Если нет ни одной версии, показываем карточку добавления
+
+
- ))}
+ )}
+
+ {/* Всегда добавляем карточку для добавления новых версий */}
+ {installedVersions.length > 0 && (
+
+
+
+ )}
)}
+
+ {/* Модальное окно для выбора версии для скачивания */}
+
+
+
+ Доступные версии для скачивания
+
+
+ {availableVersions.length === 0 ? (
+
+ Загрузка доступных версий...
+
+ ) : (
+
+ {availableVersions.map((version) => (
+ handleSelectVersion(version)}
+ >
+
+
+ ))}
+
+ )}
+
+
+
+
);
};