import React, { useState, useRef, useEffect } from 'react'; import cl from './GameSettingsModal.module.css'; import Icon from './Icon'; /** * GameDecorModal — «оформление» игры: Графика и эффекты, Стартовый экран * (Ken Burns), Экран загрузки. Вынесено из настроек во вкладку «Игра» * (TopRibbon) тремя отдельными кнопками. Открывается с нужной секцией через * проп `section` ('graphics' | 'startscreen' | 'loadingscreen'), вверху — * табы для переключения между ними. * * Props: * open — открыто ли * section — какую секцию показать первой * initial — { loading_screen:{...}, graphics:{...} } (из scene/проекта) * onClose — закрыть без сохранения * onSave(data) — data = { loading_screen, graphics } (как в GameSettingsModal) */ const MAX_IMG_BYTES = 500 * 1024; const TABS = [ { id: 'graphics', title: 'Графика', icon: 'sparkles' }, { id: 'startscreen', title: 'Стартовый экран', icon: 'loader' }, { id: 'loadingscreen', title: 'Экран загрузки', icon: 'loader' }, ]; const GameDecorModal = ({ open, section = 'graphics', initial, onClose, onSave }) => { const [tab, setTab] = useState(section); // Экран загрузки const [loadingLogo, setLoadingLogo] = useState(''); const [loadingAccent, setLoadingAccent] = useState('#ffc020'); const [loadingSpinner, setLoadingSpinner] = useState(true); const [loadingSkip, setLoadingSkip] = useState(false); // Стартовый Ken-Burns экран const [lsEnabled, setLsEnabled] = useState(true); const [lsBackground, setLsBackground] = useState(''); const [lsCover, setLsCover] = useState(''); const [lsStyle, setLsStyle] = useState('ken-burns'); const [lsPlaceName, setLsPlaceName] = useState(''); const [lsStudioName, setLsStudioName] = useState(''); const [lsVerified, setLsVerified] = useState(false); const [lsDuration, setLsDuration] = useState(2.5); const [lsProgressBar, setLsProgressBar] = useState(true); // Графика const [gfxPreset, setGfxPreset] = useState('off'); const [gfxBloom, setGfxBloom] = useState(false); const [gfxFxaa, setGfxFxaa] = useState(false); const [gfxVignette, setGfxVignette] = useState(false); const [gfxSsao, setGfxSsao] = useState(false); const [gfxSaturation, setGfxSaturation] = useState(1.0); const [gfxContrast, setGfxContrast] = useState(1.0); const [error, setError] = useState(''); const logoInputRef = useRef(null); const lsBgInputRef = useRef(null); const lsCoverInputRef = useRef(null); useEffect(() => { if (!open) return; setTab(section || 'graphics'); const ls = initial?.loading_screen || {}; setLoadingLogo(ls.logo || ''); setLoadingAccent(ls.accentColor || '#ffc020'); setLoadingSpinner(ls.defaultSpinner !== false); setLoadingSkip(!!ls.defaultSkipButton); setLsEnabled(ls.enabled !== false); setLsBackground(ls.background || ''); setLsCover(ls.cover || ''); setLsStyle(ls.style || 'ken-burns'); setLsPlaceName(ls.placeName || ''); setLsStudioName(ls.studioName || ''); setLsVerified(!!ls.verified); setLsDuration(Number.isFinite(ls.duration) && ls.duration > 0 ? ls.duration : 2.5); setLsProgressBar(ls.progressBar !== false); const gx = initial?.graphics || {}; setGfxPreset(gx.preset || 'off'); setGfxBloom(!!(gx.bloom && gx.bloom.enabled)); setGfxFxaa(!!gx.fxaa); setGfxVignette(!!(gx.vignette && gx.vignette.enabled)); setGfxSsao(!!gx.ssao); setGfxSaturation((gx.grading && Number.isFinite(gx.grading.saturation)) ? gx.grading.saturation : 1.0); setGfxContrast((gx.grading && Number.isFinite(gx.grading.contrast)) ? gx.grading.contrast : 1.0); setError(''); // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, section]); if (!open) return null; const handleLogoSelect = (e) => { const file = e.target.files?.[0]; if (!file) return; if (!/^image\/(png|jpeg|webp)$/.test(file.type)) { setError('Логотип: только PNG, JPG или WEBP'); return; } if (file.size > MAX_IMG_BYTES) { setError('Логотип слишком большой (макс. 500 КБ)'); return; } const reader = new FileReader(); reader.onload = (ev) => { setLoadingLogo(ev.target.result); setError(''); }; reader.readAsDataURL(file); }; const handleLsImage = (e, setter) => { const file = e.target.files?.[0]; if (!file) return; if (!/^image\/(png|jpeg|webp)$/.test(file.type)) { setError('Только PNG, JPG или WEBP'); return; } if (file.size > MAX_IMG_BYTES) { setError('Изображение слишком большое (макс. 500 КБ)'); return; } const reader = new FileReader(); reader.onload = (ev) => { setter(ev.target.result); setError(''); }; reader.readAsDataURL(file); }; const applyPresetToToggles = (preset) => { setGfxPreset(preset); const P = { off: { b: false, f: false, v: false, s: false, sat: 1.0, con: 1.0 }, low: { b: true, f: true, v: false, s: false, sat: 1.0, con: 1.0 }, medium: { b: true, f: true, v: true, s: false, sat: 1.1, con: 1.05 }, high: { b: true, f: true, v: true, s: true, sat: 1.2, con: 1.1 }, ultra: { b: true, f: true, v: true, s: true, sat: 1.25, con: 1.12 }, cinematic: { b: true, f: true, v: true, s: true, sat: 1.05, con: 1.18 }, vivid: { b: true, f: true, v: false, s: false, sat: 1.5, con: 1.1 }, night: { b: true, f: true, v: true, s: true, sat: 0.85, con: 1.2 }, retro: { b: false, f: false, v: true, s: false, sat: 0.7, con: 1.3 }, soft: { b: true, f: true, v: true, s: false, sat: 1.05, con: 0.95 }, }[preset]; if (!P) return; setGfxBloom(P.b); setGfxFxaa(P.f); setGfxVignette(P.v); setGfxSsao(P.s); setGfxSaturation(P.sat); setGfxContrast(P.con); }; const handleSubmit = (e) => { e.preventDefault(); onSave({ loading_screen: { logo: loadingLogo || null, accentColor: loadingAccent || '#ffc020', defaultSpinner: loadingSpinner, defaultSkipButton: loadingSkip, enabled: lsEnabled, background: lsBackground || null, cover: lsCover || null, style: lsStyle || 'ken-burns', placeName: lsPlaceName.trim(), studioName: lsStudioName.trim(), verified: lsVerified, duration: Math.max(1, Math.min(10, Number(lsDuration) || 2.5)), progressBar: lsProgressBar, }, graphics: { preset: gfxPreset, bloom: { enabled: gfxBloom }, fxaa: gfxFxaa, vignette: { enabled: gfxVignette }, ssao: gfxSsao, grading: { enabled: (gfxSaturation !== 1.0 || gfxContrast !== 1.0), saturation: Number(gfxSaturation) || 1.0, contrast: Number(gfxContrast) || 1.0, }, }, }); }; return (