112 lines
3.4 KiB
TypeScript
112 lines
3.4 KiB
TypeScript
import { useEffect } from 'react';
|
||
import { createPortal } from 'react-dom';
|
||
|
||
interface Client {
|
||
name: string;
|
||
href: string;
|
||
note?: string;
|
||
}
|
||
|
||
interface Platform {
|
||
name: string;
|
||
clients: Client[];
|
||
}
|
||
|
||
const PLATFORMS: Platform[] = [
|
||
{
|
||
name: 'iOS',
|
||
clients: [
|
||
{ name: 'Incy Client', href: 'https://apps.apple.com/us/app/incy/id6756943388', note: 'Приложение может быть недоступно в РФ, смените регион.' },
|
||
],
|
||
},
|
||
{
|
||
name: 'Windows',
|
||
clients: [
|
||
{ name: 'Happ', href: 'https://github.com/Happ-proxy/happ-desktop/releases/latest/download/setup-Happ.x64.exe' },
|
||
{
|
||
name: 'Throne',
|
||
href: 'https://github.com/throneproj/Throne/releases/latest',
|
||
note: 'При первом запуске: Настройки -> Подписка -> Разрешить отправку HWID.',
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: 'Android',
|
||
clients: [
|
||
{ name: 'Happ (Google Play)', href: 'https://play.google.com/store/apps/details?id=com.happproxy&pli=1' },
|
||
{ name: 'Happ (APK)', href: 'https://github.com/Happ-proxy/happ-android/releases/latest/download/Happ.apk' },
|
||
{ name: 'v2raytun', href: 'https://play.google.com/store/apps/details?id=com.v2raytun.android&hl=ru' },
|
||
],
|
||
},
|
||
{
|
||
name: 'Linux',
|
||
clients: [
|
||
{
|
||
name: 'Throne',
|
||
href: 'https://github.com/throneproj/Throne/releases/latest',
|
||
note: 'При первом запуске: Настройки -> Подписка -> Разрешить отправку HWID.',
|
||
},
|
||
],
|
||
},
|
||
];
|
||
|
||
interface ClientLinksModalProps {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
}
|
||
|
||
export function ClientLinksModal({ open, onClose }: ClientLinksModalProps) {
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
const onKey = (event: KeyboardEvent) => {
|
||
if (event.key === 'Escape') onClose();
|
||
};
|
||
document.addEventListener('keydown', onKey);
|
||
document.body.style.overflow = 'hidden';
|
||
return () => {
|
||
document.removeEventListener('keydown', onKey);
|
||
document.body.style.overflow = '';
|
||
};
|
||
}, [onClose, open]);
|
||
|
||
if (!open) return null;
|
||
|
||
return createPortal(
|
||
<div
|
||
className="modal-veil is-open"
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="client-links-title"
|
||
onClick={(event) => {
|
||
if (event.target === event.currentTarget) onClose();
|
||
}}
|
||
>
|
||
<div className="modal client-links-modal" role="document">
|
||
<div className="modal-kicker">Подключение устройства</div>
|
||
<h3 className="modal-title" id="client-links-title">
|
||
Выберите клиент
|
||
</h3>
|
||
<div className="client-platforms">
|
||
{PLATFORMS.map((platform) => (
|
||
<section className="client-platform" key={platform.name} aria-label={platform.name}>
|
||
<h4>{platform.name}</h4>
|
||
{platform.clients.map((client) => (
|
||
<div className="client-link" key={client.href}>
|
||
<a href={client.href} target="_blank" rel="noreferrer">
|
||
{client.name}
|
||
</a>
|
||
{client.note ? <p>{client.note}</p> : null}
|
||
</div>
|
||
))}
|
||
</section>
|
||
))}
|
||
</div>
|
||
<button type="button" className="btn" onClick={onClose} autoFocus>
|
||
Закрыть
|
||
</button>
|
||
</div>
|
||
</div>,
|
||
document.body,
|
||
);
|
||
}
|