add tray and add settings
This commit is contained in:
@ -9,7 +9,7 @@
|
|||||||
* `./src/main.js` using webpack. This gives us some performance wins.
|
* `./src/main.js` using webpack. This gives us some performance wins.
|
||||||
*/
|
*/
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { app, BrowserWindow, shell, ipcMain, Tray, Menu, nativeImage } from 'electron';
|
import { app, BrowserWindow, shell, ipcMain, Tray, Menu, nativeImage, type MenuItemConstructorOptions } from 'electron';
|
||||||
import { autoUpdater } from 'electron-updater';
|
import { autoUpdater } from 'electron-updater';
|
||||||
import log from 'electron-log';
|
import log from 'electron-log';
|
||||||
import MenuBuilder from './menu';
|
import MenuBuilder from './menu';
|
||||||
@ -71,11 +71,18 @@ const ensureTray = () => {
|
|||||||
? path.join(process.resourcesPath, 'assets')
|
? path.join(process.resourcesPath, 'assets')
|
||||||
: path.join(__dirname, '../../assets');
|
: path.join(__dirname, '../../assets');
|
||||||
|
|
||||||
const iconPath = path.join(RESOURCES_PATH, 'icon.png');
|
const getAssetPath = (...paths: string[]) => path.join(RESOURCES_PATH, ...paths);
|
||||||
const image = nativeImage.createFromPath(iconPath);
|
|
||||||
|
|
||||||
tray = new Tray(image);
|
const trayIconPath = getAssetPath('pop-popa.png'); // или 'Icons/popa-popa.png'
|
||||||
tray.setToolTip('Popa Launcher');
|
const trayImage = nativeImage.createFromPath(trayIconPath);
|
||||||
|
|
||||||
|
tray = new Tray(trayImage);
|
||||||
|
tray.setToolTip('popa-launcher');
|
||||||
|
tray.on('click', () => {
|
||||||
|
if (!mainWindow) return;
|
||||||
|
mainWindow.show();
|
||||||
|
mainWindow.focus();
|
||||||
|
});
|
||||||
|
|
||||||
const menu = Menu.buildFromTemplate([
|
const menu = Menu.buildFromTemplate([
|
||||||
{
|
{
|
||||||
@ -111,9 +118,46 @@ const applyLoginItemSettings = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let tray: Tray | null = null;
|
let tray: Tray | null = null;
|
||||||
|
let isAuthed = false;
|
||||||
|
|
||||||
let mainWindow: BrowserWindow | null = null;
|
let mainWindow: BrowserWindow | null = null;
|
||||||
|
|
||||||
|
function buildTrayMenu() {
|
||||||
|
const icon = nativeImage.createFromPath(
|
||||||
|
app.isPackaged
|
||||||
|
? path.join(__dirname, '../../assets/popa-popa.png')
|
||||||
|
: path.join(process.resourcesPath, 'assets', 'popa-popa.png'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const template: MenuItemConstructorOptions[] = [
|
||||||
|
{ label: 'popa-popa', enabled: false, icon },
|
||||||
|
{ type: 'separator' },
|
||||||
|
|
||||||
|
...(isAuthed
|
||||||
|
? ([
|
||||||
|
{ label: 'Новости', click: () => mainWindow?.webContents.send('tray-navigate', '/news') },
|
||||||
|
{ label: 'Версии', click: () => mainWindow?.webContents.send('tray-navigate', '/') },
|
||||||
|
{ label: 'Магазин', click: () => mainWindow?.webContents.send('tray-navigate', '/shop') },
|
||||||
|
{ label: 'Рынок', click: () => mainWindow?.webContents.send('tray-navigate', '/marketplace') },
|
||||||
|
{ label: 'Профиль', click: () => mainWindow?.webContents.send('tray-navigate', '/profile') },
|
||||||
|
{ label: 'Настройки', click: () => mainWindow?.webContents.send('tray-navigate', '/settings') },
|
||||||
|
{ label: 'Ежедневная награда', click: () => mainWindow?.webContents.send('tray-navigate', '/daily') },
|
||||||
|
{ label: 'Ежедневные квесты', click: () => mainWindow?.webContents.send('tray-navigate', '/dailyquests') },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Выйти', click: () => mainWindow?.webContents.send('tray-logout') },
|
||||||
|
] as MenuItemConstructorOptions[])
|
||||||
|
: ([
|
||||||
|
{ label: 'Войти', click: () => mainWindow?.webContents.send('tray-navigate', '/login') },
|
||||||
|
] as MenuItemConstructorOptions[])),
|
||||||
|
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ label: 'Показать', click: () => { mainWindow?.show(); mainWindow?.focus(); } },
|
||||||
|
{ label: 'Выход', click: () => app.quit() },
|
||||||
|
];
|
||||||
|
|
||||||
|
tray?.setContextMenu(Menu.buildFromTemplate(template));
|
||||||
|
}
|
||||||
|
|
||||||
ipcMain.on('ipc-example', async (event, arg) => {
|
ipcMain.on('ipc-example', async (event, arg) => {
|
||||||
const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`;
|
const msgTemplate = (pingPong: string) => `IPC test: ${pingPong}`;
|
||||||
console.log(msgTemplate(arg));
|
console.log(msgTemplate(arg));
|
||||||
@ -192,6 +236,8 @@ const createWindow = async () => {
|
|||||||
? path.join(process.resourcesPath, 'assets')
|
? path.join(process.resourcesPath, 'assets')
|
||||||
: path.join(__dirname, '../../assets');
|
: path.join(__dirname, '../../assets');
|
||||||
|
|
||||||
|
ensureTray();
|
||||||
|
|
||||||
const getAssetPath = (...paths: string[]): string => {
|
const getAssetPath = (...paths: string[]): string => {
|
||||||
return path.join(RESOURCES_PATH, ...paths);
|
return path.join(RESOURCES_PATH, ...paths);
|
||||||
};
|
};
|
||||||
@ -203,7 +249,7 @@ const createWindow = async () => {
|
|||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
resizable: true,
|
resizable: true,
|
||||||
frame: false,
|
frame: false,
|
||||||
icon: getAssetPath('icon.png'),
|
icon: getAssetPath('popa-popa.png'),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
webSecurity: false,
|
webSecurity: false,
|
||||||
preload: app.isPackaged
|
preload: app.isPackaged
|
||||||
@ -286,3 +332,9 @@ app
|
|||||||
ipcMain.handle('install-update', () => {
|
ipcMain.handle('install-update', () => {
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('auth-changed', (_e, payload: { isAuthed: boolean }) => {
|
||||||
|
isAuthed = Boolean(payload?.isAuthed);
|
||||||
|
buildTrayMenu();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|||||||
@ -20,6 +20,9 @@ export type Channels =
|
|||||||
| 'stop-minecraft'
|
| 'stop-minecraft'
|
||||||
| 'minecraft-started'
|
| 'minecraft-started'
|
||||||
| 'apply-launcher-settings'
|
| 'apply-launcher-settings'
|
||||||
|
| 'tray-navigate'
|
||||||
|
| 'tray-logout'
|
||||||
|
| 'auth-changed'
|
||||||
| 'minecraft-stopped';
|
| 'minecraft-stopped';
|
||||||
|
|
||||||
const electronHandler = {
|
const electronHandler = {
|
||||||
@ -42,6 +45,9 @@ const electronHandler = {
|
|||||||
invoke(channel: Channels, ...args: unknown[]): Promise<any> {
|
invoke(channel: Channels, ...args: unknown[]): Promise<any> {
|
||||||
return ipcRenderer.invoke(channel, ...args);
|
return ipcRenderer.invoke(channel, ...args);
|
||||||
},
|
},
|
||||||
|
removeAllListeners(channel: Channels) {
|
||||||
|
ipcRenderer.removeAllListeners(channel);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import { useLocation } from 'react-router-dom';
|
|||||||
import DailyReward from './pages/DailyReward';
|
import DailyReward from './pages/DailyReward';
|
||||||
import DailyQuests from './pages/DailyQuests';
|
import DailyQuests from './pages/DailyQuests';
|
||||||
import Settings from './pages/Settings';
|
import Settings from './pages/Settings';
|
||||||
|
import { TrayBridge } from './utils/TrayBridge';
|
||||||
|
|
||||||
const AuthCheck = ({ children }: { children: ReactNode }) => {
|
const AuthCheck = ({ children }: { children: ReactNode }) => {
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
||||||
@ -205,6 +206,13 @@ const AppLayout = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const raw = localStorage.getItem('launcher_config');
|
||||||
|
const isAuthed = !!raw && !!JSON.parse(raw).accessToken;
|
||||||
|
|
||||||
|
window.electron.ipcRenderer.invoke('auth-changed', { isAuthed });
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: '100vw', height: '100vh', position: 'relative', overflow: 'hidden' }}>
|
<Box sx={{ width: '100vw', height: '100vh', position: 'relative', overflow: 'hidden' }}>
|
||||||
{/* ФОН — НЕ масштабируется */}
|
{/* ФОН — НЕ масштабируется */}
|
||||||
@ -247,6 +255,7 @@ const AppLayout = () => {
|
|||||||
<TopBar onRegister={handleRegister} username={username || ''} />
|
<TopBar onRegister={handleRegister} username={username || ''} />
|
||||||
<PageHeader />
|
<PageHeader />
|
||||||
<Notifier />
|
<Notifier />
|
||||||
|
<TrayBridge />
|
||||||
|
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
BIN
src/renderer/assets/Icons/popa-popa.png
Normal file
BIN
src/renderer/assets/Icons/popa-popa.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
18
src/renderer/assets/Icons/popa-popa.svg
Normal file
18
src/renderer/assets/Icons/popa-popa.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 80 KiB |
@ -42,6 +42,8 @@ export default function CoinsDisplay({
|
|||||||
|
|
||||||
sx,
|
sx,
|
||||||
}: CoinsDisplayProps) {
|
}: CoinsDisplayProps) {
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
const [settingsVersion, setSettingsVersion] = useState(0);
|
||||||
const storageKey = useMemo(() => {
|
const storageKey = useMemo(() => {
|
||||||
// ключ под конкретного пользователя
|
// ключ под конкретного пользователя
|
||||||
return username ? `coins:${username}` : 'coins:anonymous';
|
return username ? `coins:${username}` : 'coins:anonymous';
|
||||||
@ -71,7 +73,24 @@ export default function CoinsDisplay({
|
|||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
useEffect(() => {
|
||||||
|
const handler = () => setSettingsVersion((v) => v + 1);
|
||||||
|
window.addEventListener('settings-updated', handler as EventListener);
|
||||||
|
return () => window.removeEventListener('settings-updated', handler as EventListener);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const isTooltipDisabledBySettings = useMemo(() => {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem('launcher_settings');
|
||||||
|
if (!raw) return false;
|
||||||
|
const s = JSON.parse(raw);
|
||||||
|
return Boolean(s?.disableToolTip);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}, [settingsVersion]);
|
||||||
|
|
||||||
|
const tooltipEnabled = showTooltip && !isTooltipDisabledBySettings;
|
||||||
|
|
||||||
const getSizes = () => {
|
const getSizes = () => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
@ -172,7 +191,7 @@ export default function CoinsDisplay({
|
|||||||
borderRadius: sizes.borderRadius,
|
borderRadius: sizes.borderRadius,
|
||||||
padding: sizes.containerPadding,
|
padding: sizes.containerPadding,
|
||||||
border: '1px solid rgba(255, 255, 255, 0.1)',
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||||
cursor: showTooltip ? 'help' : 'default',
|
cursor: tooltipEnabled ? 'help' : 'default',
|
||||||
|
|
||||||
// можно оставить лёгкий намёк на загрузку, но без "пульса" текста
|
// можно оставить лёгкий намёк на загрузку, но без "пульса" текста
|
||||||
opacity: isLoading ? 0.85 : 1,
|
opacity: isLoading ? 0.85 : 1,
|
||||||
@ -222,7 +241,7 @@ export default function CoinsDisplay({
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (showTooltip) {
|
if (tooltipEnabled) {
|
||||||
return (
|
return (
|
||||||
<CustomTooltip
|
<CustomTooltip
|
||||||
title={tooltipText}
|
title={tooltipText}
|
||||||
|
|||||||
@ -1,12 +1,39 @@
|
|||||||
/* eslint-disable react/jsx-props-no-spreading */
|
/* eslint-disable react/jsx-props-no-spreading */
|
||||||
|
import React, { useEffect, useState, useMemo } from 'react';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import Tooltip, { tooltipClasses, TooltipProps } from '@mui/material/Tooltip';
|
import Tooltip, { tooltipClasses, TooltipProps } from '@mui/material/Tooltip';
|
||||||
|
|
||||||
// Создаем кастомный стилизованный Tooltip с правильной типизацией
|
const STORAGE_KEY = 'launcher_settings';
|
||||||
const CustomTooltip = styled(({ className, ...props }: TooltipProps) => (
|
|
||||||
|
function readDisableTooltip(): boolean {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (!raw) return false;
|
||||||
|
const s = JSON.parse(raw);
|
||||||
|
return Boolean(s?.disableToolTip);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const readTooltipPolicy = () => {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem('launcher_settings');
|
||||||
|
if (!raw) return { disableToolTip: false, allowEssentialTooltips: true };
|
||||||
|
const s = JSON.parse(raw);
|
||||||
|
return {
|
||||||
|
disableToolTip: Boolean(s?.disableToolTip),
|
||||||
|
allowEssentialTooltips: s?.allowEssentialTooltips !== false, // default true
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return { disableToolTip: false, allowEssentialTooltips: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ВАЖНО: styled-компонент отдельно, чтобы не пересоздавался на каждый рендер
|
||||||
|
const StyledTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||||
<Tooltip {...props} classes={{ popper: className }} />
|
<Tooltip {...props} classes={{ popper: className }} />
|
||||||
))(({ theme }) => ({
|
))(() => ({
|
||||||
[`& .${tooltipClasses.tooltip}`]: {
|
[`& .${tooltipClasses.tooltip}`]: {
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
backgroundColor: 'rgba(0, 0, 0, 0.9)',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
@ -72,4 +99,29 @@ const CustomTooltip = styled(({ className, ...props }: TooltipProps) => (
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default CustomTooltip;
|
export type CustomTooltipProps = TooltipProps & {
|
||||||
|
/**
|
||||||
|
* Можно принудительно отключить тултип снаружи,
|
||||||
|
* плюс учитывается настройка disableToolTip из launcher_settings
|
||||||
|
*/
|
||||||
|
essential?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CustomTooltip(props: CustomTooltipProps) {
|
||||||
|
const { essential = false, children, ...rest } = props;
|
||||||
|
|
||||||
|
const { disableToolTip, allowEssentialTooltips } = useMemo(
|
||||||
|
() => readTooltipPolicy(),
|
||||||
|
// важно: чтобы при "Save" пересчитывалось — ты уже диспатчишь settings-updated
|
||||||
|
// поэтому ниже мы просто прочитаем ещё раз через key в местах использования (или можно слушать event тут)
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const disabledBySettings = disableToolTip && !(essential && allowEssentialTooltips);
|
||||||
|
|
||||||
|
// Если отключено — просто возвращаем children без обёртки Tooltip
|
||||||
|
if (disabledBySettings) return <>{children}</>;
|
||||||
|
|
||||||
|
return <StyledTooltip {...props}>{children}</StyledTooltip>;
|
||||||
|
}
|
||||||
|
|||||||
@ -206,6 +206,7 @@ export default function TopBar({ onRegister, username }: TopBarProps) {
|
|||||||
const logout = () => {
|
const logout = () => {
|
||||||
localStorage.removeItem('launcher_config');
|
localStorage.removeItem('launcher_config');
|
||||||
navigate('/login');
|
navigate('/login');
|
||||||
|
window.electron.ipcRenderer.invoke('auth-changed', { isAuthed: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSkin = useCallback(async () => {
|
const loadSkin = useCallback(async () => {
|
||||||
|
|||||||
@ -435,7 +435,7 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Stack direction="row" alignItems="center" spacing={1}>
|
<Stack direction="row" alignItems="center" spacing={1}>
|
||||||
<CustomTooltip title="К текущему месяцу">
|
<CustomTooltip essential title="К текущему месяцу">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={goToday}
|
onClick={goToday}
|
||||||
sx={{
|
sx={{
|
||||||
@ -661,7 +661,7 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', gap: '1.2vw', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', gap: '1.2vw', alignItems: 'center' }}>
|
||||||
<CustomTooltip title={subtitle} disableInteractive>
|
<CustomTooltip essential title={subtitle} disableInteractive>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: 'inline-block',
|
display: 'inline-block',
|
||||||
@ -701,7 +701,7 @@ export default function DailyReward({ onClaimed }: Props) {
|
|||||||
</Box>
|
</Box>
|
||||||
</CustomTooltip>
|
</CustomTooltip>
|
||||||
|
|
||||||
<CustomTooltip title="Сбросить выбор на сегодня">
|
<CustomTooltip essential title="Сбросить выбор на сегодня">
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => setSelected(today)}
|
onClick={() => setSelected(today)}
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@ -96,7 +96,7 @@ const Login = ({ onLoginSuccess }: LoginProps) => {
|
|||||||
if (onLoginSuccess) {
|
if (onLoginSuccess) {
|
||||||
onLoginSuccess(config.username);
|
onLoginSuccess(config.username);
|
||||||
}
|
}
|
||||||
|
await window.electron.ipcRenderer.invoke('auth-changed', { isAuthed: true });
|
||||||
navigate('/');
|
navigate('/');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.log(`ОШИБКА при авторизации: ${error.message}`);
|
console.log(`ОШИБКА при авторизации: ${error.message}`);
|
||||||
|
|||||||
@ -29,6 +29,8 @@ type SettingsState = {
|
|||||||
autoLaunch: boolean;
|
autoLaunch: boolean;
|
||||||
startInTray: boolean;
|
startInTray: boolean;
|
||||||
closeToTray: boolean;
|
closeToTray: boolean;
|
||||||
|
disableToolTip: boolean;
|
||||||
|
allowEssentialTooltips: boolean;
|
||||||
|
|
||||||
// Game
|
// Game
|
||||||
autoRotateSkinViewer: boolean;
|
autoRotateSkinViewer: boolean;
|
||||||
@ -73,7 +75,7 @@ const GRADIENT =
|
|||||||
backgroundColor: 'rgba(10,10,20,0.92)',
|
backgroundColor: 'rgba(10,10,20,0.92)',
|
||||||
border: '2px solid rgba(255,255,255,0.18)',
|
border: '2px solid rgba(255,255,255,0.18)',
|
||||||
boxShadow: '0 0 1.6vw rgba(233,64,205,0.35)',
|
boxShadow: '0 0 1.6vw rgba(233,64,205,0.35)',
|
||||||
transition: 'transform 0.15s ease, box-shadow 0.15s ease',
|
transition: 'transform 0.15s ease, box-shadow 0.15s ease, height 0.3s ease, width 0.3s ease',
|
||||||
'&:before': { display: 'none' },
|
'&:before': { display: 'none' },
|
||||||
'&:hover, &.Mui-focusVisible': {
|
'&:hover, &.Mui-focusVisible': {
|
||||||
width: '1.95vw',
|
width: '1.95vw',
|
||||||
@ -100,6 +102,8 @@ const defaultSettings: SettingsState = {
|
|||||||
startInTray: false,
|
startInTray: false,
|
||||||
autoLaunch: false,
|
autoLaunch: false,
|
||||||
closeToTray: true,
|
closeToTray: true,
|
||||||
|
disableToolTip: false,
|
||||||
|
allowEssentialTooltips: true,
|
||||||
|
|
||||||
autoRotateSkinViewer: true,
|
autoRotateSkinViewer: true,
|
||||||
walkingSpeed: 0.5,
|
walkingSpeed: 0.5,
|
||||||
@ -351,7 +355,7 @@ const Settings = () => {
|
|||||||
sx={{
|
sx={{
|
||||||
px: '2vw',
|
px: '2vw',
|
||||||
pb: '2vw',
|
pb: '2vw',
|
||||||
width: '100%',
|
width: '95%',
|
||||||
boxSizing: 'border-box',
|
boxSizing: 'border-box',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -567,6 +571,20 @@ const Settings = () => {
|
|||||||
checked={settings.rememberLastRoute}
|
checked={settings.rememberLastRoute}
|
||||||
onChange={setFlag('rememberLastRoute')}
|
onChange={setFlag('rememberLastRoute')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<SettingCheckboxRow
|
||||||
|
title="Отключить подсказки"
|
||||||
|
description="Отключить подсказки при наведении на элементы"
|
||||||
|
checked={settings.disableToolTip}
|
||||||
|
onChange={setFlag('disableToolTip')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SettingCheckboxRow
|
||||||
|
title="Показывать важные подсказки"
|
||||||
|
description="Некоторые подсказки нельзя отключить (важные)"
|
||||||
|
checked={settings.allowEssentialTooltips}
|
||||||
|
onChange={setFlag('allowEssentialTooltips')}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Glass>
|
</Glass>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
27
src/renderer/utils/TrayBridge.tsx
Normal file
27
src/renderer/utils/TrayBridge.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
export function TrayBridge() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onNavigate = (to: unknown) => {
|
||||||
|
navigate(String(to));
|
||||||
|
};
|
||||||
|
|
||||||
|
const onLogout = () => {
|
||||||
|
localStorage.removeItem('launcher_config');
|
||||||
|
navigate('/login');
|
||||||
|
};
|
||||||
|
|
||||||
|
window.electron.ipcRenderer.on('tray-navigate', onNavigate);
|
||||||
|
window.electron.ipcRenderer.on('tray-logout', onLogout);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.electron.ipcRenderer.removeAllListeners('tray-navigate');
|
||||||
|
window.electron.ipcRenderer.removeAllListeners('tray-logout');
|
||||||
|
};
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user