add: background, custom topbar

This commit is contained in:
2025-07-07 04:41:17 +05:00
parent 261b9ac253
commit 76917e3f90
11 changed files with 291 additions and 28 deletions

18
assets/images/heart.svg Normal file
View File

@ -0,0 +1,18 @@
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="4" width="8" height="4" fill="#FF2D0F"/>
<rect x="8" y="4" width="4" height="16" fill="#FF2D0F"/>
<rect x="4" y="8" width="4" height="8" fill="#FF2D0F"/>
<rect y="4" width="4" height="8" fill="#FF2D0F"/>
<rect x="24" y="4" width="4" height="8" fill="#FF2D0F"/>
<rect x="16" width="8" height="16" fill="#FF2D0F"/>
<rect x="16" y="16" width="4" height="4" fill="#FF2D0F"/>
<rect x="24" y="12" width="4" height="4" fill="#BD2211"/>
<rect x="20" y="16" width="4" height="4" fill="#BD2211"/>
<rect x="16" y="20" width="4" height="4" fill="#BD2211"/>
<rect x="12" y="24" width="4" height="4" fill="#BD2211"/>
<rect x="8" y="20" width="4" height="4" fill="#BD2211"/>
<rect x="4" y="16" width="4" height="4" fill="#BD2211"/>
<rect x="4" y="4" width="4" height="4" fill="#FFCAC8"/>
<rect y="12" width="4" height="4" fill="#BD2211"/>
<rect x="12" y="4" width="4" height="20" fill="#FF2D0F"/>
</svg>

After

Width:  |  Height:  |  Size: 994 B

27
package-lock.json generated
View File

@ -11,6 +11,7 @@
"@electron/notarize": "^3.0.0", "@electron/notarize": "^3.0.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.2.0",
"@mui/material": "^7.2.0", "@mui/material": "^7.2.0",
"@xmcl/core": "^2.14.1", "@xmcl/core": "^2.14.1",
"@xmcl/installer": "^6.1.0", "@xmcl/installer": "^6.1.0",
@ -3638,6 +3639,32 @@
"url": "https://opencollective.com/mui-org" "url": "https://opencollective.com/mui-org"
} }
}, },
"node_modules/@mui/icons-material": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.2.0.tgz",
"integrity": "sha512-gRCspp3pfjHQyTmSOmYw7kUQTd9Udpdan4R8EnZvqPeoAtHnPzkvjBrBqzKaoAbbBp5bGF7BcD18zZJh4nwu0A==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.27.6"
},
"engines": {
"node": ">=14.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
},
"peerDependencies": {
"@mui/material": "^7.2.0",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@mui/material": { "node_modules/@mui/material": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.2.0.tgz",

View File

@ -104,6 +104,7 @@
"@electron/notarize": "^3.0.0", "@electron/notarize": "^3.0.0",
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1", "@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.2.0",
"@mui/material": "^7.2.0", "@mui/material": "^7.2.0",
"@xmcl/core": "^2.14.1", "@xmcl/core": "^2.14.1",
"@xmcl/installer": "^6.1.0", "@xmcl/installer": "^6.1.0",

View File

@ -48,6 +48,16 @@ if (isDebug) {
require('electron-debug').default(); require('electron-debug').default();
} }
ipcMain.handle('close-app', () => {
app.quit();
return true;
});
ipcMain.handle('minimize-app', () => {
mainWindow?.minimize();
return true;
});
const installExtensions = async () => { const installExtensions = async () => {
const installer = require('electron-devtools-installer'); const installer = require('electron-devtools-installer');
const forceDownload = !!process.env.UPGRADE_EXTENSIONS; const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
@ -79,6 +89,7 @@ const createWindow = async () => {
width: 1024, width: 1024,
height: 728, height: 728,
autoHideMenuBar: true, autoHideMenuBar: true,
frame: false,
icon: getAssetPath('icon.png'), icon: getAssetPath('icon.png'),
webPreferences: { webPreferences: {
webSecurity: false, webSecurity: false,

View File

@ -772,9 +772,6 @@ export function initServerStatusHandler() {
try { try {
// Формируем адрес с портом, если указан // Формируем адрес с портом, если указан
const serverAddress = port ? `${host}:${port}` : host; const serverAddress = port ? `${host}:${port}` : host;
console.log(
`Запрос статуса сервера: ${MCSTATUS_API_URL}${serverAddress}`,
);
// Делаем запрос к API mcstatus.io // Делаем запрос к API mcstatus.io
const response = await fetch(`${MCSTATUS_API_URL}${serverAddress}`); const response = await fetch(`${MCSTATUS_API_URL}${serverAddress}`);
@ -786,7 +783,6 @@ export function initServerStatusHandler() {
} }
const data = await response.json(); const data = await response.json();
console.log('Получен ответ от API:', data);
if (data.online) { if (data.online) {
return { return {

View File

@ -5,7 +5,9 @@ export type Channels =
| 'download-progress' | 'download-progress'
| 'launch-minecraft' | 'launch-minecraft'
| 'installation-status' | 'installation-status'
| 'get-server-status'; | 'get-server-status'
| 'close-app'
| 'minimize-app';
const electronHandler = { const electronHandler = {
ipcRenderer: { ipcRenderer: {

View File

@ -16,7 +16,7 @@ body {
position: relative; position: relative;
color: white; color: white;
height: 100vh; height: 100vh;
background: linear-gradient(200.96deg, #000000, #3b4187); background: linear-gradient(242.94deg, #000000 39.07%, #3b4187 184.73%);
font-family: 'Benzin-Bold' !important; font-family: 'Benzin-Bold' !important;
overflow-y: hidden; overflow-y: hidden;
display: flex; display: flex;

View File

@ -8,6 +8,9 @@ import Login from './pages/Login';
import LaunchPage from './pages/LaunchPage'; import LaunchPage from './pages/LaunchPage';
import { ReactNode, useEffect, useState } from 'react'; import { ReactNode, useEffect, useState } from 'react';
import './App.css'; import './App.css';
import TopBar from './components/TopBar';
import { Box } from '@mui/material';
import MinecraftBackround from './components/MinecraftBackround';
// Переместите launchOptions сюда, вне компонентов // Переместите launchOptions сюда, вне компонентов
const launchOptions = { const launchOptions = {
@ -71,19 +74,38 @@ const AuthCheck = ({ children }: { children: ReactNode }) => {
}; };
const App = () => { const App = () => {
// Просто используйте window.open без useNavigate
const handleRegister = () => {
window.open('https://account.ely.by/register', '_blank');
};
return ( return (
<Router> <Router>
<Routes> <Box
<Route path="/login" element={<Login />} /> sx={{
<Route height: '100vh',
path="/" width: '100vw',
element={ position: 'relative',
<AuthCheck> display: 'flex',
<LaunchPage launchOptions={launchOptions} /> flexDirection: 'column',
</AuthCheck> alignItems: 'center',
} justifyContent: 'center',
/> }}
</Routes> >
<MinecraftBackround />
<TopBar onRegister={handleRegister} />
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/"
element={
<AuthCheck>
<LaunchPage launchOptions={launchOptions} />
</AuthCheck>
}
/>
</Routes>
</Box>
</Router> </Router>
); );
}; };

View File

@ -0,0 +1,73 @@
import { Box } from '@mui/material';
import heart from '../../../assets/images/heart.svg';
export default function MinecraftBackround() {
return (
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
opacity: 0.25,
overflow: 'hidden',
}}
>
<Box
sx={{
position: 'absolute',
bottom: 0,
right: 0,
gap: '1vw',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
rotate: '-20deg',
paddingTop: '30vw',
}}
>
<img
src={heart}
style={{ width: '20vw', height: '20vw', rotate: '-20deg' }}
/>
<img
src={heart}
style={{ width: '20vw', height: '20vw', paddingBottom: '5vw' }}
/>
<img
src={heart}
style={{ width: '20vw', height: '20vw', rotate: '20deg' }}
/>
</Box>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
gap: '1vw',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
rotate: '160deg',
paddingTop: '80vw',
}}
>
<img
src={heart}
style={{ width: '20vw', height: '20vw', rotate: '-20deg' }}
/>
<img
src={heart}
style={{ width: '20vw', height: '20vw', paddingBottom: '5vw' }}
/>
<img
src={heart}
style={{ width: '20vw', height: '20vw', rotate: '20deg' }}
/>
</Box>
</Box>
);
}

View File

@ -0,0 +1,109 @@
import { Box, Button, Typography } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import MinimizeIcon from '@mui/icons-material/Minimize';
import { useLocation } from 'react-router-dom';
declare global {
interface Window {
electron: {
ipcRenderer: {
invoke(channel: string, ...args: unknown[]): Promise<any>;
on(channel: string, func: (...args: unknown[]) => void): void;
removeAllListeners(channel: string): void;
};
};
}
}
// Определяем пропсы
interface TopBarProps {
onRegister?: () => void; // Опционально, если нужен обработчик регистрации
}
export default function TopBar({ onRegister }: TopBarProps) {
// Получаем текущий путь
const location = useLocation();
const isLoginPage = location.pathname === '/login';
return (
<Box
sx={{
display: 'flex',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '50px',
zIndex: 1000,
width: '100%',
WebkitAppRegion: 'drag',
overflow: 'hidden',
justifyContent: 'flex-end', // Всё содержимое справа
}}
>
{/* Правая часть со всеми кнопками */}
<Box
sx={{
display: 'flex',
WebkitAppRegion: 'no-drag',
gap: '2vw',
padding: '1em',
alignItems: 'center',
}}
>
{/* Кнопка регистрации, если на странице логина */}
{isLoginPage && (
<Button
variant="outlined"
color="primary"
onClick={() => onRegister && onRegister()}
sx={{
width: '10em',
height: '3em',
borderRadius: '1.5vw',
color: 'white',
backgroundImage: 'linear-gradient(to right, #7BB8FF, #FFB7ED)',
border: 'unset',
'&:hover': {
backgroundImage: 'linear-gradient(to right, #6AA8EE, #EEA7DD)',
},
boxShadow: '0.5em 0.5em 0.5em 0px #00000040 inset',
}}
>
Регистрация
</Button>
)}
{/* Кнопки управления окном */}
<Button
onClick={() => {
window.electron.ipcRenderer.invoke('minimize-app');
}}
sx={{
minWidth: 'unset',
minHeight: 'unset',
width: '3em',
height: '3em',
borderRadius: '50%',
}}
>
<MinimizeIcon sx={{ color: 'white' }} />
</Button>
<Button
onClick={() => {
window.electron.ipcRenderer.invoke('close-app');
}}
sx={{
minWidth: 'unset',
minHeight: 'unset',
width: '3em',
height: '3em',
borderRadius: '50%',
}}
>
<CloseIcon sx={{ color: 'white' }} />
</Button>
</Box>
</Box>
);
}

View File

@ -61,21 +61,25 @@ const LaunchPage = ({ launchOptions }: LaunchPageProps) => {
setDownloadProgress(progress); setDownloadProgress(progress);
setBuffer(Math.min(progress + 10, 100)); setBuffer(Math.min(progress + 10, 100));
}; };
const statusListener = (...args: unknown[]) => {
const status = args[0] as { step: string; message: string };
setInstallStep(status.step);
setInstallMessage(status.message);
};
window.electron.ipcRenderer.on('download-progress', progressListener); window.electron.ipcRenderer.on('download-progress', progressListener);
window.electron.ipcRenderer.on( window.electron.ipcRenderer.on('installation-status', statusListener);
'installation-status',
(...args: unknown[]) => {
const status = args[0] as { step: string; message: string };
setInstallStep(status.step);
setInstallMessage(status.message);
},
);
return () => { return () => {
window.electron.ipcRenderer.removeAllListeners('download-progress'); // Удаляем только конкретных слушателей, а не всех
window.electron.ipcRenderer.removeAllListeners('installation-progress'); // Это безопаснее, чем removeAllListeners
window.electron.ipcRenderer.removeAllListeners('installation-status'); const cleanup = window.electron.ipcRenderer.on;
if (typeof cleanup === 'function') {
cleanup('download-progress', progressListener);
cleanup('installation-status', statusListener);
}
// Удаляем использование removeAllListeners
}; };
}, [navigate]); }, [navigate]);