import React, { useState, useEffect } from 'react'; import cl from './GameSettingsModal.module.css'; import Icon from './Icon'; /** * ModelSaveModal — модалка сохранения пользовательской модели. * * Открывается из ModelEditorScreen при клике "Сохранить" (не автосейв). * Поля: * - Название (обязательное, до 100) * - Описание (до 500) * - Превью (берётся из текущей camera через captureThumbnail) * - Опубликовать (флаг is_public) * * Стиль повторяет GameSettingsModal (синий header, светлая тема, scrollable * body + sticky footer). Файл .module.css переиспользован полностью. * * Props: * open * initial — { title, description, is_public, thumbnail } * Если initial.thumbnail задан — используется как превью * (создан через интерактивное выделение), автоснимок НЕ делается. * onClose * onSave(data) — { title, description, is_public, thumbnail } * onCaptureThumbnail() — async () → Promise (автоснимок при открытии) * onCreatePreview() — открыть режим интерактивного выделения превью * mode — 'create' | 'edit' */ const MAX_TITLE_LEN = 100; const MAX_DESC_LEN = 500; const ModelSaveModal = ({ open, initial, onClose, onSave, onCaptureThumbnail, onCreatePreview, mode = 'create', }) => { const [title, setTitle] = useState(''); const [description, setDescription] = useState(''); const [isPublic, setIsPublic] = useState(false); const [thumbnail, setThumbnail] = useState(''); const [error, setError] = useState(''); const [capturing, setCapturing] = useState(false); useEffect(() => { if (!open) return; setTitle(initial?.title || ''); setDescription(initial?.description || ''); setIsPublic(!!initial?.is_public); setError(''); // Если превью пришло из интерактивного выделения (initial.thumbnail) — // используем его как есть, автоснимок НЕ делаем (иначе перезатрём // то, что пользователь только что закадрировал). if (initial?.thumbnail) { setThumbnail(initial.thumbnail); } else { setThumbnail(''); // Иначе при открытии автоматически снимаем актуальное превью. if (onCaptureThumbnail) { setCapturing(true); onCaptureThumbnail() .then((data) => { if (data) setThumbnail(data); }) .catch(() => {}) .finally(() => setCapturing(false)); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); if (!open) return null; const handleSubmit = (e) => { e.preventDefault(); const trimmed = title.trim(); if (!trimmed) { setError('Название обязательно'); return; } if (trimmed.length > MAX_TITLE_LEN) { setError(`Название не длиннее ${MAX_TITLE_LEN} символов`); return; } if (description.length > MAX_DESC_LEN) { setError(`Описание не длиннее ${MAX_DESC_LEN} символов`); return; } onSave({ title: trimmed, description: description.trim(), is_public: isPublic, thumbnail, }); }; return (
{ if (e.target === e.currentTarget) onClose(); }}>

{mode === 'edit' ? <> Сохранить модель : <> Сохранение модели}

{/* Превью */}
{thumbnail ? ( Превью ) : (
{capturing ? : }
)}
Превью
{/* Кнопка "Создать превью" — открывает режим интерактивного выделения области на канвасе. Только если onCreatePreview передан (из ModelEditorScreen). В режиме настроек из Toolbox её нет — превью не редактируется. */} {onCreatePreview && (
)}
{onCreatePreview ? 'Нажми «Создать превью» — затем выдели на модели квадратную область для обложки.' : 'Чтобы изменить превью — открой модель в редакторе и пересохрани.'}
{/* Название */} {/* Описание */}