feat(ui): confirm ending a show and surface help
End-session buttons, Escape, and window close share one confirm. Help is a question-mark icon on the control panel and after the version. About opens directly. Scene editor follows the UI language. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
} from '../editor/fileDrop';
|
||||
import editorStyles from '../editor/EditorApp.module.css';
|
||||
import matStyles from '../editor/MaterialsModals.module.css';
|
||||
import { useEditorI18n } from '../editor/i18n/EditorI18nContext';
|
||||
import { getDndApi } from '../shared/dndApi';
|
||||
import { Button, Input } from '../shared/ui/controls';
|
||||
import { useTokenImageUrl } from '../shared/tokens/useTokenImageUrl';
|
||||
@@ -29,6 +30,7 @@ function normalizeName(input: string): string {
|
||||
}
|
||||
|
||||
export function TokenEditModal({ open, initial, existingNames, onClose, onSaved }: Props) {
|
||||
const { t } = useEditorI18n();
|
||||
const api = getDndApi();
|
||||
const [name, setName] = useState('');
|
||||
const [filePath, setFilePath] = useState<string | null>(null);
|
||||
@@ -94,26 +96,26 @@ export function TokenEditModal({ open, initial, existingNames, onClose, onSaved
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
<button type="button" aria-label="Закрыть" onClick={onClose} className={editorStyles.modalBackdrop} />
|
||||
<button type="button" aria-label={t('common.close')} onClick={onClose} className={editorStyles.modalBackdrop} />
|
||||
<div role="dialog" aria-modal="true" className={editorStyles.modalDialog}>
|
||||
<div className={editorStyles.modalHeader}>
|
||||
<div className={editorStyles.modalTitle}>
|
||||
{initial ? 'Изменить токен' : 'Добавить токен'}
|
||||
{initial ? t('sceneEditor.editToken') : t('sceneEditor.addToken')}
|
||||
</div>
|
||||
<button type="button" aria-label="Закрыть" onClick={onClose} className={editorStyles.modalClose}>
|
||||
<button type="button" aria-label={t('common.close')} onClick={onClose} className={editorStyles.modalClose}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className={editorStyles.fieldGrid}>
|
||||
<div className={editorStyles.fieldLabel}>Название</div>
|
||||
<Input value={name} onChange={setName} placeholder="Название токена" />
|
||||
{!nameOk ? <div className={editorStyles.fieldError}>Укажите название</div> : null}
|
||||
{nameDup ? <div className={editorStyles.fieldError}>Такое название уже есть</div> : null}
|
||||
<div className={editorStyles.fieldLabel}>{t('sceneEditor.tokenName')}</div>
|
||||
<Input value={name} onChange={setName} placeholder={t('sceneEditor.tokenNamePlaceholder')} />
|
||||
{!nameOk ? <div className={editorStyles.fieldError}>{t('sceneEditor.tokenNameRequired')}</div> : null}
|
||||
{nameDup ? <div className={editorStyles.fieldError}>{t('sceneEditor.tokenNameDuplicate')}</div> : null}
|
||||
</div>
|
||||
|
||||
<div className={editorStyles.fieldGrid}>
|
||||
<div className={editorStyles.fieldLabel}>Изображение</div>
|
||||
<div className={editorStyles.fieldLabel}>{t('sceneEditor.image')}</div>
|
||||
<div
|
||||
className={[matStyles.imageDrop, drop.dragOver ? matStyles.imageDropOver : ''].join(' ')}
|
||||
onDragEnter={drop.onDragEnter}
|
||||
@@ -137,12 +139,12 @@ export function TokenEditModal({ open, initial, existingNames, onClose, onSaved
|
||||
}}
|
||||
>
|
||||
{drop.dragOver ? (
|
||||
<div className={editorStyles.dropHintOverlay}>Отпустите, чтобы загрузить изображение</div>
|
||||
<div className={editorStyles.dropHintOverlay}>{t('sceneEditor.dropImage')}</div>
|
||||
) : null}
|
||||
{previewSrc ? (
|
||||
<img src={previewSrc} alt="" className={matStyles.previewThumb} draggable={false} />
|
||||
) : (
|
||||
<div className={editorStyles.muted}>Перетащите изображение или выберите файл</div>
|
||||
<div className={editorStyles.muted}>{t('sceneEditor.dropOrPick')}</div>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => {
|
||||
@@ -153,17 +155,17 @@ export function TokenEditModal({ open, initial, existingNames, onClose, onSaved
|
||||
})();
|
||||
}}
|
||||
>
|
||||
Выбрать изображение
|
||||
{t('sceneEditor.chooseImage')}
|
||||
</Button>
|
||||
</div>
|
||||
{!hasImage ? <div className={editorStyles.fieldError}>Нужно изображение</div> : null}
|
||||
{!hasImage ? <div className={editorStyles.fieldError}>{t('sceneEditor.imageRequired')}</div> : null}
|
||||
</div>
|
||||
|
||||
{error ? <div className={editorStyles.fieldError}>{error}</div> : null}
|
||||
|
||||
<div className={editorStyles.modalFooter}>
|
||||
<Button onClick={onClose} disabled={saving}>
|
||||
Отмена
|
||||
{t('common.cancel')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
@@ -182,14 +184,14 @@ export function TokenEditModal({ open, initial, existingNames, onClose, onSaved
|
||||
onSaved(token);
|
||||
onClose();
|
||||
} catch (e) {
|
||||
setError(e instanceof Error ? e.message : 'Не удалось сохранить');
|
||||
setError(e instanceof Error ? e.message : t('sceneEditor.saveFailed'));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
})();
|
||||
}}
|
||||
>
|
||||
Сохранить
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user