v1.0.3(add quick launch, redesign shop, add new directory game)
This commit is contained in:
4
release/app/package-lock.json
generated
4
release/app/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "popa-launcher",
|
"name": "popa-launcher",
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "popa-launcher",
|
"name": "popa-launcher",
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "popa-launcher",
|
"name": "popa-launcher",
|
||||||
"version": "1.0.2",
|
"version": "1.0.3",
|
||||||
"description": "Popa Launcher",
|
"description": "Popa Launcher",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": {
|
"author": {
|
||||||
|
|||||||
@ -19,6 +19,8 @@ import { spawn } from 'child_process';
|
|||||||
import { AuthService } from './auth-service';
|
import { AuthService } from './auth-service';
|
||||||
import { API_BASE_URL } from '../renderer/api';
|
import { API_BASE_URL } from '../renderer/api';
|
||||||
|
|
||||||
|
app.setName('.popa-popa');
|
||||||
|
|
||||||
// const CDN = 'https://cdn.minecraft.popa-popa.ru';
|
// const CDN = 'https://cdn.minecraft.popa-popa.ru';
|
||||||
|
|
||||||
// const DOWNLOAD_OPTIONS = {
|
// const DOWNLOAD_OPTIONS = {
|
||||||
@ -413,8 +415,8 @@ export function initMinecraftHandlers() {
|
|||||||
preserveFiles = [], // Новый параметр: список файлов/папок для сохранения
|
preserveFiles = [], // Новый параметр: список файлов/папок для сохранения
|
||||||
} = options || {};
|
} = options || {};
|
||||||
|
|
||||||
const appPath = path.dirname(app.getPath('exe'));
|
const userDataPath = app.getPath('userData');
|
||||||
const minecraftDir = path.join(appPath, '.minecraft');
|
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||||||
const versionsDir = path.join(minecraftDir, 'versions');
|
const versionsDir = path.join(minecraftDir, 'versions');
|
||||||
const versionFilePath = path.join(minecraftDir, versionFileName);
|
const versionFilePath = path.join(minecraftDir, versionFileName);
|
||||||
|
|
||||||
@ -437,7 +439,7 @@ export function initMinecraftHandlers() {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempDir = path.join(appPath, 'temp');
|
const tempDir = path.join(userDataPath, 'temp');
|
||||||
const packDir = path.join(versionsDir, packName); // Директория пакета
|
const packDir = path.join(versionsDir, packName); // Директория пакета
|
||||||
|
|
||||||
// Создаем/очищаем временную директорию
|
// Создаем/очищаем временную директорию
|
||||||
@ -552,14 +554,14 @@ export function initMinecraftHandlers() {
|
|||||||
isVanillaVersion = false,
|
isVanillaVersion = false,
|
||||||
} = gameConfig || {};
|
} = gameConfig || {};
|
||||||
|
|
||||||
const appPath = path.dirname(app.getPath('exe'));
|
const userDataPath = app.getPath('userData');
|
||||||
const minecraftDir = path.join(appPath, '.minecraft');
|
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||||||
const versionsDir = path.join(minecraftDir, 'versions');
|
const versionsDir = path.join(minecraftDir, 'versions');
|
||||||
fs.mkdirSync(versionsDir, { recursive: true });
|
fs.mkdirSync(versionsDir, { recursive: true });
|
||||||
|
|
||||||
// gamePath:
|
// gamePath:
|
||||||
// - ваниль → .minecraft
|
// - ваниль → .popa-popa
|
||||||
// - модпак → .minecraft/versions/Comfort (или другое packName)
|
// - модпак → .popa-popa/versions/Comfort (или другое packName)
|
||||||
const packDir = isVanillaVersion
|
const packDir = isVanillaVersion
|
||||||
? minecraftDir
|
? minecraftDir
|
||||||
: path.join(versionsDir, packName);
|
: path.join(versionsDir, packName);
|
||||||
@ -802,7 +804,7 @@ export function initMinecraftHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// --- authlib-injector ---
|
// --- authlib-injector ---
|
||||||
const authlibPath = await ensureAuthlibInjectorExists(appPath);
|
const authlibPath = await ensureAuthlibInjectorExists(userDataPath);
|
||||||
console.log('authlibPath:', authlibPath);
|
console.log('authlibPath:', authlibPath);
|
||||||
|
|
||||||
event.sender.send('installation-status', {
|
event.sender.send('installation-status', {
|
||||||
@ -930,8 +932,8 @@ export function initMinecraftHandlers() {
|
|||||||
// Добавьте в функцию initMinecraftHandlers или создайте новую
|
// Добавьте в функцию initMinecraftHandlers или создайте новую
|
||||||
ipcMain.handle('get-pack-files', async (event, packName) => {
|
ipcMain.handle('get-pack-files', async (event, packName) => {
|
||||||
try {
|
try {
|
||||||
const appPath = path.dirname(app.getPath('exe'));
|
const userDataPath = app.getPath('userData');
|
||||||
const minecraftDir = path.join(appPath, '.minecraft');
|
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||||||
const packDir = path.join(minecraftDir, 'versions', packName);
|
const packDir = path.join(minecraftDir, 'versions', packName);
|
||||||
|
|
||||||
if (!fs.existsSync(packDir)) {
|
if (!fs.existsSync(packDir)) {
|
||||||
@ -974,8 +976,8 @@ export function initMinecraftHandlers() {
|
|||||||
// Сначала создаем общую функцию для получения установленных версий
|
// Сначала создаем общую функцию для получения установленных версий
|
||||||
function getInstalledVersions() {
|
function getInstalledVersions() {
|
||||||
try {
|
try {
|
||||||
const appPath = path.dirname(app.getPath('exe'));
|
const userDataPath = app.getPath('userData');
|
||||||
const minecraftDir = path.join(appPath, '.minecraft');
|
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||||||
const versionsDir = path.join(minecraftDir, 'versions');
|
const versionsDir = path.join(minecraftDir, 'versions');
|
||||||
|
|
||||||
if (!fs.existsSync(versionsDir)) {
|
if (!fs.existsSync(versionsDir)) {
|
||||||
@ -1170,8 +1172,8 @@ export function initPackConfigHandlers() {
|
|||||||
// Обработчик для сохранения настроек сборки
|
// Обработчик для сохранения настроек сборки
|
||||||
ipcMain.handle('save-pack-config', async (event, { packName, config }) => {
|
ipcMain.handle('save-pack-config', async (event, { packName, config }) => {
|
||||||
try {
|
try {
|
||||||
const appPath = path.dirname(app.getPath('exe'));
|
const userDataPath = app.getPath('userData');
|
||||||
const minecraftDir = path.join(appPath, '.minecraft');
|
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||||||
const packDir = path.join(minecraftDir, 'versions', packName);
|
const packDir = path.join(minecraftDir, 'versions', packName);
|
||||||
|
|
||||||
// Создаем папку для сборки, если она не существует
|
// Создаем папку для сборки, если она не существует
|
||||||
@ -1201,8 +1203,8 @@ export function initPackConfigHandlers() {
|
|||||||
// Обработчик для загрузки настроек сборки
|
// Обработчик для загрузки настроек сборки
|
||||||
ipcMain.handle('load-pack-config', async (event, { packName }) => {
|
ipcMain.handle('load-pack-config', async (event, { packName }) => {
|
||||||
try {
|
try {
|
||||||
const appPath = path.dirname(app.getPath('exe'));
|
const userDataPath = app.getPath('userData');
|
||||||
const minecraftDir = path.join(appPath, '.minecraft');
|
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||||||
const packDir = path.join(minecraftDir, 'versions', packName);
|
const packDir = path.join(minecraftDir, 'versions', packName);
|
||||||
const configPath = path.join(packDir, CONFIG_FILENAME);
|
const configPath = path.join(packDir, CONFIG_FILENAME);
|
||||||
|
|
||||||
@ -1246,8 +1248,8 @@ export function initPackConfigHandlers() {
|
|||||||
// Добавляем после обработчика get-available-versions
|
// Добавляем после обработчика get-available-versions
|
||||||
ipcMain.handle('get-version-config', async (event, { versionId }) => {
|
ipcMain.handle('get-version-config', async (event, { versionId }) => {
|
||||||
try {
|
try {
|
||||||
const appPath = path.dirname(app.getPath('exe'));
|
const userDataPath = app.getPath('userData');
|
||||||
const minecraftDir = path.join(appPath, '.minecraft');
|
const minecraftDir = path.join(app.getPath('userData'), 'minecraft');
|
||||||
const versionsDir = path.join(minecraftDir, 'versions');
|
const versionsDir = path.join(minecraftDir, 'versions');
|
||||||
const versionPath = path.join(versionsDir, versionId);
|
const versionPath = path.join(versionsDir, versionId);
|
||||||
|
|
||||||
|
|||||||
@ -96,8 +96,9 @@ export const BonusShopItem: React.FC<BonusShopItemProps> = ({
|
|||||||
transition: 'transform 0.35s ease, box-shadow 0.35s ease',
|
transition: 'transform 0.35s ease, box-shadow 0.35s ease',
|
||||||
|
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
transform: 'scale(1.01)',
|
// transform: 'scale(1.01)',
|
||||||
boxShadow: '0 10px 20px rgba(242,113,33,0.1)',
|
borderColor: 'rgba(200, 33, 242, 0.35)',
|
||||||
|
boxShadow: '0 1.2vw 3.2vw rgba(53, 3, 66, 0.75)',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -107,8 +108,10 @@ export const BonusShopItem: React.FC<BonusShopItemProps> = ({
|
|||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
inset: 0,
|
inset: 0,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
|
// background:
|
||||||
|
// 'radial-gradient(circle at top, rgba(242,113,33,0.25), transparent 60%)',
|
||||||
background:
|
background:
|
||||||
'radial-gradient(circle at top, rgba(242,113,33,0.25), transparent 60%)',
|
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.10), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.16), transparent 55%)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@ -67,9 +67,9 @@ export default function CapeCard({
|
|||||||
transition:
|
transition:
|
||||||
'transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease',
|
'transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
transform: 'scale(1.01)',
|
// transform: 'scale(1.01)',
|
||||||
borderColor: 'rgba(242,113,33,0.35)',
|
borderColor: 'rgba(200, 33, 242, 0.35)',
|
||||||
boxShadow: '0 1.4vw 3.8vw rgba(0,0,0,0.60)',
|
boxShadow: '0 1.2vw 3.2vw rgba(53, 3, 66, 0.75)',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -135,8 +135,8 @@ export default function CapeCard({
|
|||||||
{/* Здесь показываем ЛЕВУЮ половину текстуры (лицевую часть) */}
|
{/* Здесь показываем ЛЕВУЮ половину текстуры (лицевую часть) */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: '44vw',
|
width: '46.2vw',
|
||||||
height: '36vw',
|
height: '39.2vw',
|
||||||
minWidth: '462px',
|
minWidth: '462px',
|
||||||
minHeight: '380px',
|
minHeight: '380px',
|
||||||
imageRendering: 'pixelated',
|
imageRendering: 'pixelated',
|
||||||
|
|||||||
@ -70,8 +70,8 @@ export default function ShopItem({
|
|||||||
transition: 'transform 0.35s ease, box-shadow 0.35s ease',
|
transition: 'transform 0.35s ease, box-shadow 0.35s ease',
|
||||||
|
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
transform: 'scale(1.01)',
|
borderColor: 'rgba(200, 33, 242, 0.35)',
|
||||||
boxShadow: '0 20px 20px rgba(242,113,33,0.1)',
|
boxShadow: '0 1.2vw 3.2vw rgba(53, 3, 66, 0.75)',
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -82,7 +82,7 @@ export default function ShopItem({
|
|||||||
inset: 0,
|
inset: 0,
|
||||||
pointerEvents: 'none',
|
pointerEvents: 'none',
|
||||||
background:
|
background:
|
||||||
'radial-gradient(circle at top, rgba(242,113,33,0.25), transparent 60%)',
|
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.10), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.16), transparent 55%)',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -243,6 +243,7 @@ export default function ShopItem({
|
|||||||
fontFamily: 'Benzin-Bold',
|
fontFamily: 'Benzin-Bold',
|
||||||
borderRadius: '2.5vw',
|
borderRadius: '2.5vw',
|
||||||
fontSize: '0.85rem',
|
fontSize: '0.85rem',
|
||||||
|
color: 'white',
|
||||||
'&:hover': {
|
'&:hover': {
|
||||||
transform: 'scale(1.02)',
|
transform: 'scale(1.02)',
|
||||||
},
|
},
|
||||||
|
|||||||
@ -24,6 +24,9 @@ import { useTheme } from '@mui/material/styles';
|
|||||||
import InventoryIcon from '@mui/icons-material/Inventory';
|
import InventoryIcon from '@mui/icons-material/Inventory';
|
||||||
import { RiCoupon3Fill } from 'react-icons/ri';
|
import { RiCoupon3Fill } from 'react-icons/ri';
|
||||||
|
|
||||||
|
import type { NotificationPosition } from '../components/Notifications/CustomNotification';
|
||||||
|
import { isNotificationsEnabled, getNotifPositionFromSettings } from '../utils/notifications';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: {
|
electron: {
|
||||||
@ -75,10 +78,48 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ===== QUICK LAUNCH ===== \\
|
||||||
|
const [lastVersion, setLastVersion] = useState<null | any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem('last_launched_version');
|
||||||
|
if (!raw) return;
|
||||||
|
|
||||||
|
setLastVersion(JSON.parse(raw));
|
||||||
|
} catch {
|
||||||
|
setLastVersion(null);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
// ===== QUICK LAUNCH ===== \\
|
||||||
|
|
||||||
const path = location.pathname || '';
|
const path = location.pathname || '';
|
||||||
const isAuthPage =
|
const isAuthPage =
|
||||||
path.startsWith('/login') || path.startsWith('/registration');
|
path.startsWith('/login') || path.startsWith('/registration');
|
||||||
|
|
||||||
|
const [notifOpen, setNotifOpen] = useState(false);
|
||||||
|
const [notifMsg, setNotifMsg] = useState<React.ReactNode>('');
|
||||||
|
const [notifSeverity, setNotifSeverity] = useState<
|
||||||
|
'success' | 'info' | 'warning' | 'error'
|
||||||
|
>('info');
|
||||||
|
|
||||||
|
const [notifPos, setNotifPos] = useState<NotificationPosition>({
|
||||||
|
vertical: 'bottom',
|
||||||
|
horizontal: 'center',
|
||||||
|
});
|
||||||
|
|
||||||
|
const showNotification = (
|
||||||
|
message: React.ReactNode,
|
||||||
|
severity: 'success' | 'info' | 'warning' | 'error' = 'info',
|
||||||
|
position: NotificationPosition = getNotifPositionFromSettings(),
|
||||||
|
) => {
|
||||||
|
if (!isNotificationsEnabled()) return;
|
||||||
|
setNotifMsg(message);
|
||||||
|
setNotifSeverity(severity);
|
||||||
|
setNotifPos(position);
|
||||||
|
setNotifOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const TAB_ROUTES: Array<{
|
const TAB_ROUTES: Array<{
|
||||||
value: number;
|
value: number;
|
||||||
match: (p: string) => boolean;
|
match: (p: string) => boolean;
|
||||||
@ -267,6 +308,66 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
window.removeEventListener('settings-updated', handler as EventListener);
|
window.removeEventListener('settings-updated', handler as EventListener);
|
||||||
}, [updateGradientVars]);
|
}, [updateGradientVars]);
|
||||||
|
|
||||||
|
const handleQuickLaunch = async () => {
|
||||||
|
const raw = localStorage.getItem('last_launched_version');
|
||||||
|
if (!raw) {
|
||||||
|
showNotification('Вы не запускали ни одну из сборок!', 'warning');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = JSON.parse(raw);
|
||||||
|
|
||||||
|
const savedConfig = JSON.parse(
|
||||||
|
localStorage.getItem('launcher_config') || '{}',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!savedConfig.accessToken) {
|
||||||
|
showNotification('Вы не авторизованы', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await window.electron.ipcRenderer.invoke('launch-minecraft', {
|
||||||
|
accessToken: savedConfig.accessToken,
|
||||||
|
uuid: savedConfig.uuid,
|
||||||
|
username: savedConfig.username,
|
||||||
|
|
||||||
|
memory: ctx.memory,
|
||||||
|
baseVersion: ctx.baseVersion,
|
||||||
|
fabricVersion: ctx.fabricVersion,
|
||||||
|
packName: ctx.packName,
|
||||||
|
serverIp: ctx.serverIp,
|
||||||
|
|
||||||
|
isVanillaVersion: ctx.isVanillaVersion,
|
||||||
|
versionToLaunchOverride: ctx.versionToLaunchOverride,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLastLaunchLabel = (v: any) => {
|
||||||
|
if (!v) return '';
|
||||||
|
|
||||||
|
const title = v.isVanillaVersion
|
||||||
|
? `Minecraft ${v.versionId}`
|
||||||
|
: `Сборка ${v.packName}`;
|
||||||
|
|
||||||
|
const details = [
|
||||||
|
v.baseVersion ? `MC ${v.baseVersion}` : null,
|
||||||
|
v.memory ? `${v.memory} MB RAM` : null,
|
||||||
|
]
|
||||||
|
.filter(Boolean)
|
||||||
|
.join(' · ');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '0.2vw' }}>
|
||||||
|
<Typography sx={{ fontSize: '0.9vw', fontWeight: 600 }}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Typography sx={{ fontSize: '0.75vw', opacity: 0.7 }}>
|
||||||
|
{details}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
className={isAuthPage ? undefined : 'glass-ui'}
|
className={isAuthPage ? undefined : 'glass-ui'}
|
||||||
@ -435,6 +536,78 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
marginRight: '1vw',
|
marginRight: '1vw',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{lastVersion &&
|
||||||
|
<CustomTooltip
|
||||||
|
title={getLastLaunchLabel(lastVersion)}
|
||||||
|
arrow
|
||||||
|
placement="bottom"
|
||||||
|
TransitionProps={{ timeout: 120 }}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
onClick={handleQuickLaunch}
|
||||||
|
disableRipple
|
||||||
|
disableFocusRipple
|
||||||
|
sx={{
|
||||||
|
minWidth: 'unset',
|
||||||
|
width: '3vw',
|
||||||
|
height: '3vw',
|
||||||
|
borderRadius: '3vw',
|
||||||
|
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
overflow: 'hidden',
|
||||||
|
|
||||||
|
px: '0.8vw',
|
||||||
|
|
||||||
|
background:
|
||||||
|
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.20), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.16), transparent 55%), rgba(10,10,20,0.92)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
boxShadow: '0 1.4vw 3.8vw rgba(0,0,0,0.55)',
|
||||||
|
color: 'white',
|
||||||
|
backdropFilter: 'blur(14px)',
|
||||||
|
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
|
||||||
|
'& .quick-text': {
|
||||||
|
opacity: 0,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
marginRight: '0.6vw',
|
||||||
|
fontSize: '0.9vw',
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
transform: 'translateX(10px)',
|
||||||
|
transition: 'all 0.25s ease',
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:hover': {
|
||||||
|
width: '16.5vw',
|
||||||
|
transform: 'scale(1.05)',
|
||||||
|
|
||||||
|
'& .quick-text': {
|
||||||
|
opacity: 1,
|
||||||
|
transform: 'translateX(0)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'&:after': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
left: '0%',
|
||||||
|
right: '0%',
|
||||||
|
bottom: 0,
|
||||||
|
height: '0.15vw',
|
||||||
|
borderRadius: '999px',
|
||||||
|
background:
|
||||||
|
'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)',
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="quick-text">Быстрый запуск</span>
|
||||||
|
<span style={{ fontSize: '1vw' }}>⚡</span>
|
||||||
|
</Button>
|
||||||
|
</CustomTooltip>
|
||||||
|
}
|
||||||
{!isLoginPage && !isRegistrationPage && username && (
|
{!isLoginPage && !isRegistrationPage && username && (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: '1vw' }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: '1vw' }}>
|
||||||
<HeadAvatar
|
<HeadAvatar
|
||||||
|
|||||||
@ -115,6 +115,21 @@ const LaunchPage = ({
|
|||||||
|
|
||||||
const minecraftStartedListener = () => {
|
const minecraftStartedListener = () => {
|
||||||
setIsGameRunning(true);
|
setIsGameRunning(true);
|
||||||
|
|
||||||
|
const raw = localStorage.getItem('pending_launch_context');
|
||||||
|
if (!raw) return;
|
||||||
|
|
||||||
|
const context = JSON.parse(raw);
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
'last_launched_version',
|
||||||
|
JSON.stringify({
|
||||||
|
...context,
|
||||||
|
launchedAt: Date.now(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
localStorage.removeItem('pending_launch_context');
|
||||||
};
|
};
|
||||||
|
|
||||||
const minecraftStoppedListener = () => {
|
const minecraftStoppedListener = () => {
|
||||||
@ -246,6 +261,11 @@ const LaunchPage = ({
|
|||||||
setIsDownloading(true);
|
setIsDownloading(true);
|
||||||
setBuffer(10);
|
setBuffer(10);
|
||||||
|
|
||||||
|
if (isGameRunning) {
|
||||||
|
showNotification('Minecraft уже запущен', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Используем настройки выбранной версии или дефолтные
|
// Используем настройки выбранной версии или дефолтные
|
||||||
const currentConfig = versionConfig || {
|
const currentConfig = versionConfig || {
|
||||||
packName: versionId || 'Comfort',
|
packName: versionId || 'Comfort',
|
||||||
@ -312,6 +332,25 @@ const LaunchPage = ({
|
|||||||
versionToLaunchOverride: isVanillaVersion ? versionId : undefined,
|
versionToLaunchOverride: isVanillaVersion ? versionId : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const launchContext = {
|
||||||
|
versionId,
|
||||||
|
packName: versionId || currentConfig.packName,
|
||||||
|
|
||||||
|
baseVersion: currentConfig.baseVersion,
|
||||||
|
fabricVersion: currentConfig.fabricVersion,
|
||||||
|
serverIp: currentConfig.serverIp,
|
||||||
|
|
||||||
|
isVanillaVersion,
|
||||||
|
versionToLaunchOverride: isVanillaVersion ? versionId : undefined,
|
||||||
|
|
||||||
|
memory: config.memory,
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
'pending_launch_context',
|
||||||
|
JSON.stringify(launchContext),
|
||||||
|
);
|
||||||
|
|
||||||
const launchResult = await window.electron.ipcRenderer.invoke(
|
const launchResult = await window.electron.ipcRenderer.invoke(
|
||||||
'launch-minecraft',
|
'launch-minecraft',
|
||||||
options,
|
options,
|
||||||
@ -328,6 +367,12 @@ const LaunchPage = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('beforeunload', () => {
|
||||||
|
localStorage.removeItem('pending_launch_context');
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleStopMinecraft = async () => {
|
const handleStopMinecraft = async () => {
|
||||||
try {
|
try {
|
||||||
const result = await window.electron.ipcRenderer.invoke('stop-minecraft');
|
const result = await window.electron.ipcRenderer.invoke('stop-minecraft');
|
||||||
|
|||||||
@ -59,6 +59,8 @@ import {
|
|||||||
} from '../utils/notifications';
|
} from '../utils/notifications';
|
||||||
import { translateServer } from '../utils/serverTranslator';
|
import { translateServer } from '../utils/serverTranslator';
|
||||||
import { Server } from 'http';
|
import { Server } from 'http';
|
||||||
|
import CapeCard from '../components/CapeCard'
|
||||||
|
import CoinsDisplay from '../components/CoinsDisplay';
|
||||||
|
|
||||||
interface TabPanelProps {
|
interface TabPanelProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -189,6 +191,8 @@ export default function Shop() {
|
|||||||
const [prankServerId, setPrankServerId] = useState<string>('');
|
const [prankServerId, setPrankServerId] = useState<string>('');
|
||||||
const [prankProcessing, setPrankProcessing] = useState(false);
|
const [prankProcessing, setPrankProcessing] = useState(false);
|
||||||
|
|
||||||
|
const [hoveredPrankId, setHoveredPrankId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Уведомления
|
// Уведомления
|
||||||
const [notification, setNotification] = useState<{
|
const [notification, setNotification] = useState<{
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -948,34 +952,22 @@ export default function Shop() {
|
|||||||
|
|
||||||
<TabPanel value={tabValue} index={2}>
|
<TabPanel value={tabValue} index={2}>
|
||||||
{/* Блок плащей */}
|
{/* Блок плащей */}
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
||||||
<Typography
|
|
||||||
variant="h6"
|
|
||||||
sx={{
|
|
||||||
fontFamily: 'Benzin-Bold',
|
|
||||||
backgroundImage:
|
|
||||||
'linear-gradient(136deg, rgb(242,113,33), rgb(233,64,87), rgb(138,35,135))',
|
|
||||||
WebkitBackgroundClip: 'text',
|
|
||||||
WebkitTextFillColor: 'transparent',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Плащи
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
{availableCapes.length > 0 ? (
|
{availableCapes.length > 0 ? (
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{availableCapes.map((cape) => (
|
{availableCapes.map((cape) => (
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={cape.id}>
|
<Grid item xs={12} sm={6} md={4} lg={3} key={cape.id}>
|
||||||
<ShopItem
|
<CapeCard
|
||||||
type="cape"
|
key={cape.id}
|
||||||
id={cape.id}
|
cape={{
|
||||||
name={cape.name}
|
id: cape.id,
|
||||||
description={cape.description}
|
name: cape.name,
|
||||||
imageUrl={cape.image_url}
|
description: cape.description,
|
||||||
price={cape.price}
|
image_url: cape.image_url,
|
||||||
disabled={false}
|
price: cape.price,
|
||||||
playerSkinUrl={playerSkinUrl}
|
}}
|
||||||
onClick={() => handlePurchaseCape(cape.id)}
|
mode="shop"
|
||||||
|
onAction={() => handlePurchaseCape(cape.id)}
|
||||||
|
actionDisabled={false}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
@ -983,118 +975,131 @@ export default function Shop() {
|
|||||||
) : (
|
) : (
|
||||||
<Typography>У вас уже есть все доступные плащи!</Typography>
|
<Typography>У вас уже есть все доступные плащи!</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
|
|
||||||
<TabPanel value={tabValue} index={3}>
|
<TabPanel value={tabValue} index={3}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
{prankCommands.map((cmd) => (
|
{prankCommands.map((cmd) => {
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3} key={cmd.id}>
|
const isHovered = hoveredPrankId === cmd.id; // 👈 ВОТ СЮДА
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: '20vw',
|
|
||||||
height: '100%',
|
|
||||||
borderRadius: '1.4vw',
|
|
||||||
background:
|
|
||||||
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.14), transparent 55%), rgba(10,10,20,0.88)',
|
|
||||||
border: '1px solid rgba(255,255,255,0.10)',
|
|
||||||
boxShadow: '0 1.2vw 3.2vw rgba(0,0,0,0.55)',
|
|
||||||
backdropFilter: 'blur(14px)',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
overflow: 'hidden',
|
|
||||||
transition: 'transform 0.18s ease',
|
|
||||||
'&:hover': {
|
|
||||||
transform: 'scale(1.03)',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Картинка */}
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
height: '9vw',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
background: 'rgba(0,0,0,0.25)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
component="img"
|
|
||||||
src={`https://cdn.minecraft.popa-popa.ru/textures/${cmd.material?.toLowerCase()}.png`}
|
|
||||||
alt={cmd.material}
|
|
||||||
sx={{
|
|
||||||
width: '6vw',
|
|
||||||
height: '6vw',
|
|
||||||
imageRendering: 'pixelated',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Контент */}
|
return (
|
||||||
|
<Grid item xs={12} sm={6} md={4} lg={3} key={cmd.id}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
p: '1vw',
|
width: '20vw',
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: '1.4vw',
|
||||||
|
background:
|
||||||
|
'radial-gradient(circle at 10% 10%, rgba(242,113,33,0.10), transparent 55%), radial-gradient(circle at 90% 20%, rgba(233,64,205,0.16), transparent 55%)',
|
||||||
|
border: '1px solid rgba(255,255,255,0.10)',
|
||||||
|
boxShadow: '0 1.2vw 3.2vw rgba(0,0,0,0.55)',
|
||||||
|
backdropFilter: 'blur(14px)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
gap: '0.6vw',
|
overflow: 'hidden',
|
||||||
flexGrow: 1,
|
transition: 'all 0.18s ease',
|
||||||
|
'&:hover': {
|
||||||
|
// transform: 'scale(1.03)',
|
||||||
|
borderColor: 'rgba(200, 33, 242, 0.35)',
|
||||||
|
boxShadow: '0 1.2vw 3.2vw rgba(53, 3, 66, 0.75)',
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography
|
{/* Картинка */}
|
||||||
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
fontFamily: 'Benzin-Bold',
|
height: '15vw',
|
||||||
fontSize: '1.1vw',
|
display: 'flex',
|
||||||
background: GRADIENT,
|
alignItems: 'center',
|
||||||
WebkitBackgroundClip: 'text',
|
justifyContent: 'center',
|
||||||
WebkitTextFillColor: 'transparent',
|
background: 'rgba(0,0,0,0.25)',
|
||||||
|
borderBottom: '1px solid rgba(255,255,255,0.05)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{cmd.name}
|
<Box
|
||||||
</Typography>
|
component="img"
|
||||||
|
src={`https://cdn.minecraft.popa-popa.ru/textures/${cmd.material?.toLowerCase()}.png`}
|
||||||
|
alt={cmd.material}
|
||||||
|
sx={{
|
||||||
|
width: '12vw',
|
||||||
|
height: '12vw',
|
||||||
|
imageRendering: 'pixelated',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Typography
|
{/* Контент */}
|
||||||
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
color: 'rgba(255,255,255,0.75)',
|
p: '1vw',
|
||||||
fontSize: '0.95vw',
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: '0.6vw',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{cmd.description}
|
<Typography
|
||||||
</Typography>
|
sx={{
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
fontSize: '1.1vw',
|
||||||
|
background: GRADIENT,
|
||||||
|
WebkitBackgroundClip: 'text',
|
||||||
|
WebkitTextFillColor: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cmd.name}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 900,
|
color: 'rgba(255,255,255,0.75)',
|
||||||
fontSize: '1vw',
|
fontSize: '0.95vw',
|
||||||
}}
|
flexGrow: 1,
|
||||||
>
|
}}
|
||||||
Цена: {cmd.price} монет
|
>
|
||||||
</Typography>
|
{cmd.description}
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<Button
|
{/* <Box
|
||||||
disableRipple
|
sx={{
|
||||||
onClick={() => {
|
display: 'flex',
|
||||||
setSelectedPrank(cmd);
|
gap: '1vw',
|
||||||
setPrankTarget('');
|
alignItems: 'center'
|
||||||
setPrankDialogOpen(true);
|
}}
|
||||||
}}
|
>
|
||||||
sx={{
|
<Typography>Цена:</Typography> <CoinsDisplay value={cmd.price} size="small" />
|
||||||
mt: '0.6vw',
|
</Box> */}
|
||||||
borderRadius: '999px',
|
|
||||||
fontFamily: 'Benzin-Bold',
|
<Button
|
||||||
color: '#fff',
|
onMouseEnter={() => setHoveredPrankId(cmd.id)}
|
||||||
background: GRADIENT,
|
onMouseLeave={() => setHoveredPrankId(null)}
|
||||||
py: '0.5vw',
|
disableRipple
|
||||||
'&:hover': { filter: 'brightness(1.05)' },
|
onClick={() => {
|
||||||
}}
|
setSelectedPrank(cmd);
|
||||||
>
|
setPrankTarget('');
|
||||||
Купить
|
setPrankDialogOpen(true);
|
||||||
</Button>
|
}}
|
||||||
|
sx={{
|
||||||
|
mt: '0.6vw',
|
||||||
|
borderRadius: '999px',
|
||||||
|
fontFamily: 'Benzin-Bold',
|
||||||
|
color: '#fff',
|
||||||
|
background: GRADIENT,
|
||||||
|
py: '0.5vw',
|
||||||
|
'&:hover': { filter: 'brightness(1.05)' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isHovered ? (
|
||||||
|
'Купить'
|
||||||
|
) : (
|
||||||
|
<CoinsDisplay showTooltip={false} value={cmd.price} size="small" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Grid>
|
||||||
</Grid>
|
);
|
||||||
))}
|
})}
|
||||||
</Grid>
|
</Grid>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user