diff --git a/release/app/package-lock.json b/release/app/package-lock.json index 7b8f009..3739063 100644 --- a/release/app/package-lock.json +++ b/release/app/package-lock.json @@ -1,12 +1,12 @@ { "name": "popa-launcher", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "popa-launcher", - "version": "1.0.2", + "version": "1.0.3", "hasInstallScript": true, "license": "MIT" } diff --git a/release/app/package.json b/release/app/package.json index 5afde3d..84f277b 100644 --- a/release/app/package.json +++ b/release/app/package.json @@ -1,6 +1,6 @@ { "name": "popa-launcher", - "version": "1.0.2", + "version": "1.0.3", "description": "Popa Launcher", "license": "MIT", "author": { diff --git a/src/main/minecraft-launcher.ts b/src/main/minecraft-launcher.ts index 1aacbe9..95c2d1b 100644 --- a/src/main/minecraft-launcher.ts +++ b/src/main/minecraft-launcher.ts @@ -19,6 +19,8 @@ import { spawn } from 'child_process'; import { AuthService } from './auth-service'; import { API_BASE_URL } from '../renderer/api'; +app.setName('.popa-popa'); + // const CDN = 'https://cdn.minecraft.popa-popa.ru'; // const DOWNLOAD_OPTIONS = { @@ -413,8 +415,8 @@ export function initMinecraftHandlers() { preserveFiles = [], // Новый параметр: список файлов/папок для сохранения } = options || {}; - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); + const userDataPath = app.getPath('userData'); + const minecraftDir = path.join(app.getPath('userData'), 'minecraft'); const versionsDir = path.join(minecraftDir, 'versions'); 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); // Директория пакета // Создаем/очищаем временную директорию @@ -552,14 +554,14 @@ export function initMinecraftHandlers() { isVanillaVersion = false, } = gameConfig || {}; - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); + const userDataPath = app.getPath('userData'); + const minecraftDir = path.join(app.getPath('userData'), 'minecraft'); const versionsDir = path.join(minecraftDir, 'versions'); fs.mkdirSync(versionsDir, { recursive: true }); // gamePath: - // - ваниль → .minecraft - // - модпак → .minecraft/versions/Comfort (или другое packName) + // - ваниль → .popa-popa + // - модпак → .popa-popa/versions/Comfort (или другое packName) const packDir = isVanillaVersion ? minecraftDir : path.join(versionsDir, packName); @@ -802,7 +804,7 @@ export function initMinecraftHandlers() { } // --- authlib-injector --- - const authlibPath = await ensureAuthlibInjectorExists(appPath); + const authlibPath = await ensureAuthlibInjectorExists(userDataPath); console.log('authlibPath:', authlibPath); event.sender.send('installation-status', { @@ -930,8 +932,8 @@ export function initMinecraftHandlers() { // Добавьте в функцию initMinecraftHandlers или создайте новую ipcMain.handle('get-pack-files', async (event, packName) => { try { - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); + const userDataPath = app.getPath('userData'); + const minecraftDir = path.join(app.getPath('userData'), 'minecraft'); const packDir = path.join(minecraftDir, 'versions', packName); if (!fs.existsSync(packDir)) { @@ -974,8 +976,8 @@ export function initMinecraftHandlers() { // Сначала создаем общую функцию для получения установленных версий function getInstalledVersions() { try { - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); + const userDataPath = app.getPath('userData'); + const minecraftDir = path.join(app.getPath('userData'), 'minecraft'); const versionsDir = path.join(minecraftDir, 'versions'); if (!fs.existsSync(versionsDir)) { @@ -1170,8 +1172,8 @@ export function initPackConfigHandlers() { // Обработчик для сохранения настроек сборки ipcMain.handle('save-pack-config', async (event, { packName, config }) => { try { - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); + const userDataPath = app.getPath('userData'); + const minecraftDir = path.join(app.getPath('userData'), 'minecraft'); const packDir = path.join(minecraftDir, 'versions', packName); // Создаем папку для сборки, если она не существует @@ -1201,8 +1203,8 @@ export function initPackConfigHandlers() { // Обработчик для загрузки настроек сборки ipcMain.handle('load-pack-config', async (event, { packName }) => { try { - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); + const userDataPath = app.getPath('userData'); + const minecraftDir = path.join(app.getPath('userData'), 'minecraft'); const packDir = path.join(minecraftDir, 'versions', packName); const configPath = path.join(packDir, CONFIG_FILENAME); @@ -1246,8 +1248,8 @@ export function initPackConfigHandlers() { // Добавляем после обработчика get-available-versions ipcMain.handle('get-version-config', async (event, { versionId }) => { try { - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); + const userDataPath = app.getPath('userData'); + const minecraftDir = path.join(app.getPath('userData'), 'minecraft'); const versionsDir = path.join(minecraftDir, 'versions'); const versionPath = path.join(versionsDir, versionId); diff --git a/src/renderer/components/BonusShopItem.tsx b/src/renderer/components/BonusShopItem.tsx index ac7cc16..02acbc1 100644 --- a/src/renderer/components/BonusShopItem.tsx +++ b/src/renderer/components/BonusShopItem.tsx @@ -96,8 +96,9 @@ export const BonusShopItem: React.FC = ({ transition: 'transform 0.35s ease, box-shadow 0.35s ease', '&:hover': { - transform: 'scale(1.01)', - boxShadow: '0 10px 20px rgba(242,113,33,0.1)', + // transform: 'scale(1.01)', + 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 = ({ position: 'absolute', inset: 0, pointerEvents: 'none', + // background: + // 'radial-gradient(circle at top, rgba(242,113,33,0.25), transparent 60%)', 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%)', }} /> diff --git a/src/renderer/components/CapeCard.tsx b/src/renderer/components/CapeCard.tsx index f8b176f..758966a 100644 --- a/src/renderer/components/CapeCard.tsx +++ b/src/renderer/components/CapeCard.tsx @@ -67,9 +67,9 @@ export default function CapeCard({ transition: 'transform 0.18s ease, border-color 0.18s ease, box-shadow 0.18s ease', '&:hover': { - transform: 'scale(1.01)', - borderColor: 'rgba(242,113,33,0.35)', - boxShadow: '0 1.4vw 3.8vw rgba(0,0,0,0.60)', + // transform: 'scale(1.01)', + borderColor: 'rgba(200, 33, 242, 0.35)', + boxShadow: '0 1.2vw 3.2vw rgba(53, 3, 66, 0.75)', }, }} > @@ -135,8 +135,8 @@ export default function CapeCard({ {/* Здесь показываем ЛЕВУЮ половину текстуры (лицевую часть) */} @@ -82,7 +82,7 @@ export default function ShopItem({ inset: 0, pointerEvents: 'none', 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', borderRadius: '2.5vw', fontSize: '0.85rem', + color: 'white', '&:hover': { transform: 'scale(1.02)', }, diff --git a/src/renderer/components/TopBar.tsx b/src/renderer/components/TopBar.tsx index aa20bc1..867784f 100644 --- a/src/renderer/components/TopBar.tsx +++ b/src/renderer/components/TopBar.tsx @@ -24,6 +24,9 @@ import { useTheme } from '@mui/material/styles'; import InventoryIcon from '@mui/icons-material/Inventory'; import { RiCoupon3Fill } from 'react-icons/ri'; +import type { NotificationPosition } from '../components/Notifications/CustomNotification'; +import { isNotificationsEnabled, getNotifPositionFromSettings } from '../utils/notifications'; + declare global { interface Window { electron: { @@ -75,10 +78,48 @@ export default function TopBar({ onRegister, username }: TopBarProps) { null, ); + // ===== QUICK LAUNCH ===== \\ + const [lastVersion, setLastVersion] = useState(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 isAuthPage = path.startsWith('/login') || path.startsWith('/registration'); + const [notifOpen, setNotifOpen] = useState(false); + const [notifMsg, setNotifMsg] = useState(''); + const [notifSeverity, setNotifSeverity] = useState< + 'success' | 'info' | 'warning' | 'error' + >('info'); + + const [notifPos, setNotifPos] = useState({ + 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<{ value: number; match: (p: string) => boolean; @@ -267,6 +308,66 @@ export default function TopBar({ onRegister, username }: TopBarProps) { window.removeEventListener('settings-updated', handler as EventListener); }, [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 ( + + + {title} + + + {details} + + + ); + }; + return ( + {lastVersion && + + + + } {!isLoginPage && !isRegistrationPage && username && ( { 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 = () => { @@ -246,6 +261,11 @@ const LaunchPage = ({ setIsDownloading(true); setBuffer(10); + if (isGameRunning) { + showNotification('Minecraft уже запущен', 'info'); + return; + } + // Используем настройки выбранной версии или дефолтные const currentConfig = versionConfig || { packName: versionId || 'Comfort', @@ -312,6 +332,25 @@ const LaunchPage = ({ 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( 'launch-minecraft', options, @@ -328,6 +367,12 @@ const LaunchPage = ({ } }; + useEffect(() => { + window.addEventListener('beforeunload', () => { + localStorage.removeItem('pending_launch_context'); + }); + }, []); + const handleStopMinecraft = async () => { try { const result = await window.electron.ipcRenderer.invoke('stop-minecraft'); diff --git a/src/renderer/pages/Shop.tsx b/src/renderer/pages/Shop.tsx index 26b215c..46b511b 100644 --- a/src/renderer/pages/Shop.tsx +++ b/src/renderer/pages/Shop.tsx @@ -59,6 +59,8 @@ import { } from '../utils/notifications'; import { translateServer } from '../utils/serverTranslator'; import { Server } from 'http'; +import CapeCard from '../components/CapeCard' +import CoinsDisplay from '../components/CoinsDisplay'; interface TabPanelProps { children?: React.ReactNode; @@ -189,6 +191,8 @@ export default function Shop() { const [prankServerId, setPrankServerId] = useState(''); const [prankProcessing, setPrankProcessing] = useState(false); + const [hoveredPrankId, setHoveredPrankId] = useState(null); + // Уведомления const [notification, setNotification] = useState<{ open: boolean; @@ -948,34 +952,22 @@ export default function Shop() { {/* Блок плащей */} - - - Плащи - - {availableCapes.length > 0 ? ( {availableCapes.map((cape) => ( - handlePurchaseCape(cape.id)} + handlePurchaseCape(cape.id)} + actionDisabled={false} /> ))} @@ -983,118 +975,131 @@ export default function Shop() { ) : ( У вас уже есть все доступные плащи! )} - - {prankCommands.map((cmd) => ( - - - {/* Картинка */} - - - + {prankCommands.map((cmd) => { + const isHovered = hoveredPrankId === cmd.id; // 👈 ВОТ СЮДА - {/* Контент */} + return ( + - - {cmd.name} - + + - - {cmd.description} - + + {cmd.name} + - - Цена: {cmd.price} монет - + + {cmd.description} + - + {/* + Цена: + */} + + + - - - ))} + + ); + })}