Files
DndGamePlayer/app/renderer/editor/about/EditorAboutModals.tsx
Ivan Fontosh a29c72b307 feat: окно инструкций, свёрнутая история и обновление спонсоров
Инструкции вынесены в отдельное окно Electron; токены игроков не пропадают при смене сцены; предпросмотр и плейлисты адаптированы; история свёрнута по умолчанию.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 06:21:42 +08:00

119 lines
4.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect } from 'react';
import { createPortal } from 'react-dom';
import { APP_DISPLAY_NAME_EN, APP_DISPLAY_NAME_RU } from '../../../shared/appBranding';
import { Button } from '../../shared/ui/controls';
import styles from '../EditorApp.module.css';
import { useEditorI18n } from '../i18n/EditorI18nContext';
const ABOUT_SPONSORS = [
{ kind: 'text', name: 'Тифлинг бард 😈❤️' },
{
kind: 'link',
name: 'Клуб настольных и бизнес-игр "КвестИя" - Подчуфарова Евгения Александровна',
url: 'https://vk.ru/club210361604',
},
{ kind: 'link', name: 'Старые кости', url: 'https://boosty.to/tabletoppodcasts' },
{ kind: 'text', name: 'Майер С.В. (Клуб «Серый Май»)' },
{ kind: 'link', name: 'true_rus(КАВ)', url: 'https://truecorp.su/' },
{ kind: 'text', name: 'Макс LonelyFlame Чурсин' },
] as const;
type AppAboutModalProps = {
open: boolean;
onClose: () => void;
appVersion: string | null;
};
export function AppAboutModal({ open, onClose, appVersion }: AppAboutModalProps) {
const { t, locale } = useEditorI18n();
const appName = locale === 'ru' ? APP_DISPLAY_NAME_RU : APP_DISPLAY_NAME_EN;
useEffect(() => {
if (!open) return;
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [onClose, open]);
if (!open) return null;
return createPortal(
<>
<button
type="button"
aria-label={t('common.close')}
onClick={onClose}
className={styles.modalBackdrop}
/>
<div role="dialog" aria-modal="true" className={styles.modalDialog}>
<div className={styles.modalHeader}>
<div className={styles.modalTitle}>{t('app.about.title')}</div>
<button
type="button"
aria-label={t('common.close')}
onClick={onClose}
className={styles.modalClose}
>
×
</button>
</div>
<div className={styles.aboutBody}>
<div className={styles.aboutAppName}>{appName}</div>
<div className={styles.aboutTagline}>{t('app.about.tagline')}</div>
<p className={styles.aboutParagraph}>{t('app.about.description')}</p>
<div className={styles.aboutMetaGrid}>
<div className={styles.fieldLabel}>{t('app.about.versionLabel')}</div>
<div>{appVersion ?? '—'}</div>
<div className={styles.fieldLabel}>{t('app.about.developerLabel')}</div>
<div>{t('app.about.developer')}</div>
<div className={styles.fieldLabel}>{t('app.about.supportLabel')}</div>
<div>
<a className={styles.aboutLink} href={`mailto:${t('app.about.supportEmail')}`}>
{t('app.about.supportEmail')}
</a>
</div>
<div className={styles.fieldLabel}>{t('app.about.websiteLabel')}</div>
<div>
<a
className={styles.aboutLink}
href={t('app.about.websiteUrl')}
target="_blank"
rel="noreferrer"
>
{t('app.about.websiteUrl')}
</a>
</div>
<div className={styles.fieldLabel}>{t('app.about.sponsorsLabel')}</div>
<div className={styles.aboutSponsorsList}>
{ABOUT_SPONSORS.map((sponsor) =>
sponsor.kind === 'link' ? (
<a
key={sponsor.name}
className={styles.aboutLink}
href={sponsor.url}
target="_blank"
rel="noreferrer"
>
{sponsor.name}
</a>
) : (
<div key={sponsor.name}>{sponsor.name}</div>
),
)}
</div>
</div>
</div>
<div className={styles.modalFooter}>
<Button variant="primary" onClick={onClose}>
{t('common.close')}
</Button>
</div>
</div>
</>,
document.body,
);
}