Files
DndGamePlayer/app/renderer/editor/about/EditorAboutModals.tsx
T
Ivan Fontosh de9190959c feat(editor): add Run help hint and rename from project list
Show a help icon when Run is disabled, open the relevant instruction section
on click, and position its tooltip below-left. Add Rename project to the home
screen project card menu.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 22:45:19 +08:00

186 lines
5.9 KiB
TypeScript
Raw 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 React, { useEffect, useState } 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 {
HELP_SECTION_IDS,
helpSectionBodyKey,
helpSectionTitleKey,
type HelpSectionId,
} from '../help/helpSections';
import { useEditorI18n } from '../i18n/EditorI18nContext';
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>
</div>
<div className={styles.modalFooter}>
<Button variant="primary" onClick={onClose}>
{t('common.close')}
</Button>
</div>
</div>
</>,
document.body,
);
}
type InstructionsModalProps = {
open: boolean;
onClose: () => void;
initialSection?: HelpSectionId;
};
function InstructionsModalBody({
initialSection,
onClose,
}: {
initialSection: HelpSectionId;
onClose: () => void;
}) {
const { t } = useEditorI18n();
const [activeId, setActiveId] = useState<HelpSectionId>(initialSection);
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose();
};
window.addEventListener('keydown', onKey);
return () => window.removeEventListener('keydown', onKey);
}, [onClose]);
const body = t(helpSectionBodyKey(activeId));
const paragraphs = body.split('\n\n').filter((p) => p.trim() !== '');
return createPortal(
<>
<button
type="button"
aria-label={t('common.close')}
onClick={onClose}
className={styles.modalBackdrop}
/>
<div role="dialog" aria-modal="true" className={styles.instructionsDialog}>
<div className={styles.modalHeader}>
<div className={styles.modalTitle}>{t('help.title')}</div>
<button
type="button"
aria-label={t('common.close')}
onClick={onClose}
className={styles.modalClose}
>
×
</button>
</div>
<div className={styles.instructionsLayout}>
<div className={styles.instructionsContent}>
<div className={styles.instructionsContentTitle}>{t(helpSectionTitleKey(activeId))}</div>
{paragraphs.map((p, i) => (
<p key={i} className={styles.instructionsParagraph}>
{p}
</p>
))}
</div>
<nav className={styles.instructionsNav} aria-label={t('help.navAria')}>
{HELP_SECTION_IDS.map((id) => (
<button
key={id}
type="button"
className={[
styles.instructionsNavItem,
id === activeId ? styles.instructionsNavItemActive : '',
]
.filter(Boolean)
.join(' ')}
aria-current={id === activeId ? 'true' : undefined}
onClick={() => setActiveId(id)}
>
{t(helpSectionTitleKey(id))}
</button>
))}
</nav>
</div>
<div className={styles.modalFooter}>
<Button variant="primary" onClick={onClose}>
{t('common.close')}
</Button>
</div>
</div>
</>,
document.body,
);
}
export function InstructionsModal({ open, onClose, initialSection }: InstructionsModalProps) {
if (!open) return null;
const section = initialSection ?? 'overview';
return <InstructionsModalBody key={section} initialSection={section} onClose={onClose} />;
}