feat: improved launch settings
This commit is contained in:
@ -18,6 +18,7 @@ import {
|
||||
initMinecraftHandlers,
|
||||
initAuthHandlers,
|
||||
initServerStatusHandler,
|
||||
initPackConfigHandlers,
|
||||
} from './minecraft-launcher';
|
||||
|
||||
class AppUpdater {
|
||||
@ -133,6 +134,7 @@ const createWindow = async () => {
|
||||
initAuthHandlers();
|
||||
initMinecraftHandlers();
|
||||
initServerStatusHandler();
|
||||
initPackConfigHandlers();
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -477,7 +477,7 @@ export function initMinecraftHandlers() {
|
||||
accessToken,
|
||||
uuid,
|
||||
username,
|
||||
memory = 2048,
|
||||
memory = 4096,
|
||||
baseVersion = '1.21.4',
|
||||
fabricVersion = 'fabric0.16.14',
|
||||
packName = 'Comfort', // Название основной сборки
|
||||
@ -761,6 +761,47 @@ export function initMinecraftHandlers() {
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Добавьте в функцию initMinecraftHandlers или создайте новую
|
||||
ipcMain.handle('get-pack-files', async (event, packName) => {
|
||||
try {
|
||||
const appPath = path.dirname(app.getPath('exe'));
|
||||
const minecraftDir = path.join(appPath, '.minecraft');
|
||||
const packDir = path.join(minecraftDir, 'versions', packName);
|
||||
|
||||
if (!fs.existsSync(packDir)) {
|
||||
return { success: false, error: 'Директория сборки не найдена' };
|
||||
}
|
||||
|
||||
// Функция для рекурсивного обхода директории
|
||||
const scanDir: any = (dir: any, basePath: any = '') => {
|
||||
const result = [];
|
||||
const items = fs.readdirSync(dir);
|
||||
|
||||
for (const item of items) {
|
||||
const itemPath = path.join(dir, item);
|
||||
const relativePath = basePath ? path.join(basePath, item) : item;
|
||||
const isDirectory = fs.statSync(itemPath).isDirectory();
|
||||
|
||||
result.push({
|
||||
name: item,
|
||||
path: relativePath,
|
||||
isDirectory,
|
||||
// Если это директория, рекурсивно сканируем ее
|
||||
children: isDirectory ? scanDir(itemPath, relativePath) : [],
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const files = scanDir(packDir);
|
||||
return { success: true, files };
|
||||
} catch (error) {
|
||||
console.error('Ошибка при получении файлов сборки:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Добавляем обработчики IPC для аутентификации
|
||||
@ -855,3 +896,84 @@ export function initServerStatusHandler() {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Функция для работы с конфигурацией сборки
|
||||
export function initPackConfigHandlers() {
|
||||
// Файл конфигурации
|
||||
const CONFIG_FILENAME = 'popa-launcher-config.json';
|
||||
|
||||
// Обработчик для сохранения настроек сборки
|
||||
ipcMain.handle('save-pack-config', async (event, { packName, config }) => {
|
||||
try {
|
||||
const appPath = path.dirname(app.getPath('exe'));
|
||||
const minecraftDir = path.join(appPath, '.minecraft');
|
||||
const packDir = path.join(minecraftDir, 'versions', packName);
|
||||
|
||||
// Создаем папку для сборки, если она не существует
|
||||
if (!fs.existsSync(packDir)) {
|
||||
fs.mkdirSync(packDir, { recursive: true });
|
||||
}
|
||||
|
||||
const configPath = path.join(packDir, CONFIG_FILENAME);
|
||||
|
||||
// Сохраняем конфигурацию в файл
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
|
||||
// Добавляем файл конфигурации в список файлов, которые не удаляются
|
||||
if (!config.preserveFiles.includes(CONFIG_FILENAME)) {
|
||||
config.preserveFiles.push(CONFIG_FILENAME);
|
||||
// Перезаписываем файл с обновленным списком
|
||||
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Ошибка при сохранении настроек сборки:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик для загрузки настроек сборки
|
||||
ipcMain.handle('load-pack-config', async (event, { packName }) => {
|
||||
try {
|
||||
const appPath = path.dirname(app.getPath('exe'));
|
||||
const minecraftDir = path.join(appPath, '.minecraft');
|
||||
const packDir = path.join(minecraftDir, 'versions', packName);
|
||||
const configPath = path.join(packDir, CONFIG_FILENAME);
|
||||
|
||||
// Проверяем существование файла конфигурации
|
||||
if (!fs.existsSync(configPath)) {
|
||||
// Если файла нет, возвращаем дефолтную конфигурацию
|
||||
return {
|
||||
success: true,
|
||||
config: {
|
||||
memory: 4096,
|
||||
preserveFiles: [CONFIG_FILENAME], // По умолчанию сохраняем файл конфигурации
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Читаем и парсим конфигурацию
|
||||
const configData = fs.readFileSync(configPath, 'utf8');
|
||||
const config = JSON.parse(configData);
|
||||
|
||||
// Добавляем файл конфигурации в список сохраняемых файлов, если его там нет
|
||||
if (!config.preserveFiles.includes(CONFIG_FILENAME)) {
|
||||
config.preserveFiles.push(CONFIG_FILENAME);
|
||||
}
|
||||
|
||||
return { success: true, config };
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке настроек сборки:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
// Возвращаем дефолтную конфигурацию при ошибке
|
||||
config: {
|
||||
memory: 4096,
|
||||
preserveFiles: [CONFIG_FILENAME],
|
||||
},
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -7,7 +7,9 @@ export type Channels =
|
||||
| 'installation-status'
|
||||
| 'get-server-status'
|
||||
| 'close-app'
|
||||
| 'minimize-app';
|
||||
| 'minimize-app'
|
||||
| 'save-pack-config'
|
||||
| 'load-pack-config';
|
||||
|
||||
const electronHandler = {
|
||||
ipcRenderer: {
|
||||
|
@ -23,13 +23,6 @@ const launchOptions = {
|
||||
baseVersion: '1.21.4',
|
||||
serverIp: 'popa-popa.ru',
|
||||
fabricVersion: 'fabric0.16.14',
|
||||
preserveFiles: [
|
||||
'options.txt',
|
||||
'screenshots',
|
||||
'schematics',
|
||||
'syncmatics',
|
||||
'saves',
|
||||
],
|
||||
};
|
||||
|
||||
const AuthCheck = ({ children }: { children: ReactNode }) => {
|
||||
|
205
src/renderer/components/FilesSelector.tsx
Normal file
205
src/renderer/components/FilesSelector.tsx
Normal file
@ -0,0 +1,205 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
Typography,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemText,
|
||||
Collapse,
|
||||
CircularProgress,
|
||||
} from '@mui/material';
|
||||
import FolderIcon from '@mui/icons-material/Folder';
|
||||
import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
|
||||
|
||||
interface FileNode {
|
||||
name: string;
|
||||
path: string;
|
||||
isDirectory: boolean;
|
||||
children: FileNode[];
|
||||
}
|
||||
|
||||
interface FilesSelectorProps {
|
||||
packName: string;
|
||||
onSelectionChange: (selectedFiles: string[]) => void;
|
||||
initialSelected?: string[]; // Добавляем этот параметр
|
||||
}
|
||||
|
||||
export default function FilesSelector({
|
||||
packName,
|
||||
onSelectionChange,
|
||||
initialSelected = [], // Значение по умолчанию
|
||||
}: FilesSelectorProps) {
|
||||
const [files, setFiles] = useState<FileNode[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
// Используем initialSelected для начального состояния
|
||||
const [selectedFiles, setSelectedFiles] = useState<string[]>(initialSelected);
|
||||
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchFiles = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const result = await window.electron.ipcRenderer.invoke(
|
||||
'get-pack-files',
|
||||
packName,
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
setFiles(result.files);
|
||||
} else {
|
||||
setError(result.error);
|
||||
}
|
||||
} catch (err) {
|
||||
setError('Ошибка при загрузке файлов');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFiles();
|
||||
}, [packName]);
|
||||
|
||||
// Обработка выбора файла/папки
|
||||
const handleToggle = (
|
||||
path: string,
|
||||
isDirectory: boolean,
|
||||
children: FileNode[],
|
||||
) => {
|
||||
let newSelected = [...selectedFiles];
|
||||
|
||||
if (isDirectory) {
|
||||
if (selectedFiles.includes(path)) {
|
||||
// Если папка выбрана, убираем ее и все вложенные файлы
|
||||
newSelected = newSelected.filter((p) => !p.startsWith(path));
|
||||
} else {
|
||||
// Если папка не выбрана, добавляем ее и все вложенные файлы
|
||||
newSelected.push(path);
|
||||
const addChildPaths = (nodes: FileNode[]) => {
|
||||
for (const node of nodes) {
|
||||
newSelected.push(node.path);
|
||||
if (node.isDirectory) {
|
||||
addChildPaths(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
addChildPaths(children);
|
||||
}
|
||||
} else {
|
||||
// Для обычного файла просто переключаем состояние
|
||||
if (selectedFiles.includes(path)) {
|
||||
newSelected = newSelected.filter((p) => p !== path);
|
||||
} else {
|
||||
newSelected.push(path);
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedFiles(newSelected);
|
||||
onSelectionChange(newSelected);
|
||||
};
|
||||
|
||||
// Переключение раскрытия папки
|
||||
const toggleFolder = (path: string) => {
|
||||
const newExpanded = new Set(expandedFolders);
|
||||
if (expandedFolders.has(path)) {
|
||||
newExpanded.delete(path);
|
||||
} else {
|
||||
newExpanded.add(path);
|
||||
}
|
||||
setExpandedFolders(newExpanded);
|
||||
};
|
||||
|
||||
// Рекурсивный компонент для отображения файлов и папок
|
||||
const renderFileTree = (nodes: FileNode[]) => {
|
||||
// Сортировка: сначала папки, потом файлы
|
||||
const sortedNodes = [...nodes].sort((a, b) => {
|
||||
// Если у элементов разные типы (папка/файл)
|
||||
if (a.isDirectory !== b.isDirectory) {
|
||||
return a.isDirectory ? -1 : 1; // Папки идут первыми
|
||||
}
|
||||
// Если оба элемента одного типа, сортируем по алфавиту
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return (
|
||||
<List dense>
|
||||
{sortedNodes.map((node) => (
|
||||
<div key={node.path}>
|
||||
<ListItem
|
||||
sx={{
|
||||
borderRadius: '3vw',
|
||||
backgroundColor: '#FFFFFF1A',
|
||||
marginBottom: '1vh',
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
checked={selectedFiles.includes(node.path)}
|
||||
onChange={() =>
|
||||
handleToggle(node.path, node.isDirectory, node.children)
|
||||
}
|
||||
tabIndex={-1}
|
||||
sx={{ color: 'white' }}
|
||||
/>
|
||||
</ListItemIcon>
|
||||
|
||||
{node.isDirectory && (
|
||||
<ListItemIcon onClick={() => toggleFolder(node.path)}>
|
||||
{expandedFolders.has(node.path) ? (
|
||||
<ExpandLessIcon sx={{ color: 'white' }} />
|
||||
) : (
|
||||
<ExpandMoreIcon sx={{ color: 'white' }} />
|
||||
)}
|
||||
</ListItemIcon>
|
||||
)}
|
||||
|
||||
<ListItemIcon>
|
||||
{node.isDirectory ? (
|
||||
<FolderIcon sx={{ color: 'white' }} />
|
||||
) : (
|
||||
<InsertDriveFileIcon sx={{ color: 'white' }} />
|
||||
)}
|
||||
</ListItemIcon>
|
||||
|
||||
<ListItemText
|
||||
primary={node.name}
|
||||
sx={{ color: 'white', fontFamily: 'Benzin-Bold' }}
|
||||
/>
|
||||
</ListItem>
|
||||
|
||||
{node.isDirectory && node.children.length > 0 && (
|
||||
<Collapse
|
||||
in={expandedFolders.has(node.path)}
|
||||
timeout="auto"
|
||||
unmountOnExit
|
||||
>
|
||||
<Box sx={{ pl: 4 }}>{renderFileTree(node.children)}</Box>
|
||||
</Collapse>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return <CircularProgress />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Typography color="error">{error}</Typography>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{ maxHeight: '300px', overflow: 'auto' }}>
|
||||
{renderFileTree(files)}
|
||||
</Box>
|
||||
);
|
||||
}
|
@ -33,7 +33,6 @@ const useConfig = () => {
|
||||
return {
|
||||
username: '',
|
||||
password: '',
|
||||
memory: 4096,
|
||||
comfortVersion: '',
|
||||
accessToken: '',
|
||||
clientToken: '',
|
||||
|
@ -5,11 +5,16 @@ import {
|
||||
Snackbar,
|
||||
Alert,
|
||||
LinearProgress,
|
||||
Modal,
|
||||
} from '@mui/material';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
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';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
@ -34,12 +39,19 @@ interface LaunchPageProps {
|
||||
baseVersion: string;
|
||||
serverIp: string;
|
||||
fabricVersion: string;
|
||||
preserveFiles: string[];
|
||||
};
|
||||
}
|
||||
|
||||
const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
const navigate = useNavigate();
|
||||
// Начальное состояние должно быть пустым или с минимальными значениями
|
||||
const [config, setConfig] = useState<{
|
||||
memory: number;
|
||||
preserveFiles: string[];
|
||||
}>({
|
||||
memory: 0,
|
||||
preserveFiles: [],
|
||||
});
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||
const [buffer, setBuffer] = useState(10);
|
||||
@ -51,6 +63,9 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
}>({ open: false, message: '', severity: 'info' });
|
||||
const [installStep, setInstallStep] = useState('');
|
||||
const [installMessage, setInstallMessage] = useState('');
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const handleOpen = () => setOpen(true);
|
||||
const handleClose = () => setOpen(false);
|
||||
|
||||
useEffect(() => {
|
||||
const savedConfig = localStorage.getItem('launcher_config');
|
||||
@ -85,6 +100,29 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
};
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
// Загрузка конфигурации сборки при монтировании
|
||||
const loadPackConfig = async () => {
|
||||
try {
|
||||
const result = await window.electron.ipcRenderer.invoke(
|
||||
'load-pack-config',
|
||||
{
|
||||
packName: launchOptions.packName,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.success && result.config) {
|
||||
// Полностью заменяем config значениями из файла
|
||||
setConfig(result.config);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Ошибка при загрузке настроек:', error);
|
||||
}
|
||||
};
|
||||
|
||||
loadPackConfig();
|
||||
}, [launchOptions.packName]);
|
||||
|
||||
const showNotification = (
|
||||
message: string,
|
||||
severity: 'success' | 'error' | 'info',
|
||||
@ -102,6 +140,19 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
setDownloadProgress(0);
|
||||
setBuffer(10);
|
||||
|
||||
// Загружаем настройки сборки
|
||||
const result = await window.electron.ipcRenderer.invoke(
|
||||
'load-pack-config',
|
||||
{
|
||||
packName: launchOptions.packName,
|
||||
},
|
||||
);
|
||||
|
||||
// Используйте уже существующий state вместо локальной переменной
|
||||
if (result.success && result.config) {
|
||||
setConfig(result.config); // Обновляем state
|
||||
}
|
||||
|
||||
const savedConfig = JSON.parse(
|
||||
localStorage.getItem('launcher_config') || '{}',
|
||||
);
|
||||
@ -112,7 +163,7 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
apiReleaseUrl: launchOptions.apiReleaseUrl,
|
||||
versionFileName: launchOptions.versionFileName,
|
||||
packName: launchOptions.packName,
|
||||
preserveFiles: launchOptions.preserveFiles,
|
||||
preserveFiles: config.preserveFiles,
|
||||
};
|
||||
|
||||
// Передаем опции для скачивания
|
||||
@ -142,7 +193,7 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
accessToken: savedConfig.accessToken,
|
||||
uuid: savedConfig.uuid,
|
||||
username: savedConfig.username,
|
||||
memory: launchOptions.memory,
|
||||
memory: config.memory, // Используем state
|
||||
baseVersion: launchOptions.baseVersion,
|
||||
packName: launchOptions.packName,
|
||||
serverIp: launchOptions.serverIp,
|
||||
@ -180,6 +231,29 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Функция для сохранения настроек
|
||||
const savePackConfig = async () => {
|
||||
try {
|
||||
const configToSave = {
|
||||
memory: config.memory,
|
||||
preserveFiles: config.preserveFiles || [],
|
||||
};
|
||||
|
||||
await window.electron.ipcRenderer.invoke('save-pack-config', {
|
||||
packName: launchOptions.packName,
|
||||
config: configToSave,
|
||||
});
|
||||
|
||||
// Обновляем launchOptions
|
||||
launchOptions.memory = config.memory;
|
||||
|
||||
showNotification('Настройки сохранены', 'success');
|
||||
} catch (error) {
|
||||
console.error('Ошибка при сохранении настроек:', error);
|
||||
showNotification('Ошибка сохранения настроек', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ gap: '1vh', display: 'flex', flexDirection: 'column' }}>
|
||||
<PopaPopa />
|
||||
@ -235,13 +309,46 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
</Box>
|
||||
</Box>
|
||||
) : (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: '1vw',
|
||||
width: '100%', // родитель занимает всю ширину
|
||||
}}
|
||||
>
|
||||
{/* Первая кнопка — растягивается на всё доступное пространство */}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={handleLaunchMinecraft}
|
||||
sx={{
|
||||
flexGrow: 1, // занимает всё свободное место
|
||||
width: 'auto', // ширина подстраивается
|
||||
borderRadius: '3vw',
|
||||
fontFamily: 'Benzin-Bold',
|
||||
background: 'linear-gradient(90deg, #3B96FF 0%, #FFB7ED 100%)',
|
||||
}}
|
||||
>
|
||||
Запустить Minecraft
|
||||
</Button>
|
||||
|
||||
{/* Вторая кнопка — квадратная, фиксированного размера (ширина = высоте) */}
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
flexShrink: 0, // не сжимается
|
||||
aspectRatio: '1', // ширина = высоте
|
||||
backgroundColor: 'grey',
|
||||
borderRadius: '3vw',
|
||||
minHeight: 'unset',
|
||||
minWidth: 'unset',
|
||||
height: '100%', // занимает полную высоту родителя
|
||||
}}
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<SettingsIcon />
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Snackbar
|
||||
@ -257,6 +364,66 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
|
||||
{notification.message}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
aria-labelledby="modal-modal-title"
|
||||
aria-describedby="modal-modal-description"
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: 400,
|
||||
background:
|
||||
'linear-gradient(-242.94deg, #000000 39.07%, #3b4187 184.73%)',
|
||||
border: '2px solid #000',
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
borderRadius: '3vw',
|
||||
gap: '1vh',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<Typography id="modal-modal-title" variant="body1" component="h2">
|
||||
Файлы и папки, которые будут сохранены после переустановки сборки
|
||||
</Typography>
|
||||
<FilesSelector
|
||||
packName={launchOptions.packName}
|
||||
initialSelected={config.preserveFiles} // Передаем текущие выбранные файлы
|
||||
onSelectionChange={(selected) => {
|
||||
setConfig((prev) => ({ ...prev, preserveFiles: selected }));
|
||||
}}
|
||||
/>
|
||||
<Typography variant="body1" sx={{ color: 'white' }}>
|
||||
Оперативная память выделенная для Minecraft
|
||||
</Typography>
|
||||
<MemorySlider
|
||||
memory={config.memory}
|
||||
onChange={(e, value) => {
|
||||
setConfig((prev) => ({ ...prev, memory: value as number }));
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
onClick={() => {
|
||||
savePackConfig();
|
||||
handleClose();
|
||||
}}
|
||||
sx={{
|
||||
borderRadius: '3vw',
|
||||
fontFamily: 'Benzin-Bold',
|
||||
}}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Box>
|
||||
</Modal>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Box, Typography } from '@mui/material';
|
||||
import useConfig from '../hooks/useConfig';
|
||||
import useAuth from '../hooks/useAuth';
|
||||
import AuthForm from '../components/Login/AuthForm';
|
||||
import MemorySlider from '../components/Login/MemorySlider';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import PopaPopa from '../components/popa-popa';
|
||||
import useConfig from '../hooks/useConfig';
|
||||
|
||||
const Login = () => {
|
||||
const navigate = useNavigate();
|
||||
|
Reference in New Issue
Block a user