import { useEffect, useRef, useState } from 'react'; import SkinViewer from '../components/SkinViewer'; import { fetchPlayer, uploadSkin, fetchCapes, Cape, activateCape, deactivateCape, } from '../api'; import { Box, Typography, Paper, Button, FormControl, InputLabel, Select, MenuItem, Alert, } from '@mui/material'; import CapeCard from '../components/CapeCard'; import { FullScreenLoader } from '../components/FullScreenLoader'; import { OnlinePlayersPanel } from '../components/OnlinePlayersPanel'; import DailyRewards from '../components/Profile/DailyRewards'; import CustomNotification from '../components/Notifications/CustomNotification'; import type { NotificationPosition } from '../components/Notifications/CustomNotification'; import { useNavigate } from 'react-router-dom'; import { isNotificationsEnabled, getNotifPositionFromSettings } from '../utils/notifications'; export default function Profile() { const navigate = useNavigate(); const fileInputRef = useRef(null); const [skin, setSkin] = useState(''); const [cape, setCape] = useState(''); const [username, setUsername] = useState(''); const [skinFile, setSkinFile] = useState(null); const [skinModel, setSkinModel] = useState(''); // slim или classic const [uploadStatus, setUploadStatus] = useState< 'idle' | 'loading' | 'success' | 'error' >('idle'); const [statusMessage, setStatusMessage] = useState(''); const [isDragOver, setIsDragOver] = useState(false); const [capes, setCapes] = useState([]); const [uuid, setUuid] = useState(''); const [loading, setLoading] = useState(false); const [viewerWidth, setViewerWidth] = useState(500); const [viewerHeight, setViewerHeight] = useState(600); // notification const [notifOpen, setNotifOpen] = useState(false); const [notifMsg, setNotifMsg] = useState(''); const [notifSeverity, setNotifSeverity] = useState< 'success' | 'info' | 'warning' | 'error' >('success'); const [notifPos, setNotifPos] = useState({ vertical: 'top', horizontal: 'right', }); const [autoRotate, setAutoRotate] = useState(true); const [walkingSpeed, setWalkingSpeed] = useState(0.5); useEffect(() => { const savedConfig = localStorage.getItem('launcher_config'); if (savedConfig) { const config = JSON.parse(savedConfig); if (config.uuid) { loadPlayerData(config.uuid); setUsername(config.username || ''); loadCapesData(config.username || ''); setUuid(config.uuid || ''); } } }, []); useEffect(() => { // Функция для обновления размеров const updateDimensions = () => { setViewerWidth(window.innerWidth * 0.4); // 25vw setViewerHeight(window.innerWidth * 0.5); // 30vw }; // Вызываем один раз при монтировании updateDimensions(); // Добавляем слушатель изменения размера окна window.addEventListener('resize', updateDimensions); // Очистка при размонтировании return () => { window.removeEventListener('resize', updateDimensions); }; }, []); const loadPlayerData = async (uuid: string) => { try { setLoading(true); const player = await fetchPlayer(uuid); setSkin(player.skin_url); setCape(player.cloak_url); setLoading(false); } catch (error) { console.error('Ошибка при получении данных игрока:', error); setSkin(''); setCape(''); } }; const loadCapesData = async (username: string) => { try { setLoading(true); const capesData = await fetchCapes(username); setCapes(capesData); setLoading(false); } catch (error) { console.error('Ошибка при получении плащей:', error); setCapes([]); } }; // Обработка перетаскивания файла const handleFileDrop = (e: React.DragEvent) => { e.preventDefault(); setIsDragOver(false); if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { const file = e.dataTransfer.files[0]; if (file.type === 'image/png') { setSkinFile(file); setStatusMessage(`Файл "${file.name}" готов к загрузке`); } else { setStatusMessage('Пожалуйста, выберите файл в формате PNG'); setUploadStatus('error'); } } }; // Обработка выбора файла const handleFileSelect = (e: React.ChangeEvent) => { if (e.target.files && e.target.files.length > 0) { const file = e.target.files[0]; if (file.type === 'image/png') { setSkinFile(file); setStatusMessage(`Файл "${file.name}" готов к загрузке`); } else { setStatusMessage('Пожалуйста, выберите файл в формате PNG'); setUploadStatus('error'); } } }; const handleActivateCape = async (cape_id: string) => { setLoading(true); await activateCape(username, cape_id); await loadCapesData(username); setLoading(false); }; const handleDeactivateCape = async (cape_id: string) => { setLoading(true); await deactivateCape(username, cape_id); await loadCapesData(username); await loadPlayerData(uuid); setLoading(false); }; // Отправка запроса на установку скина const handleUploadSkin = async () => { setLoading(true); if (!skinFile || !username) { const msg = 'Необходимо выбрать файл и указать имя пользователя'; setStatusMessage(msg); setUploadStatus('error'); setLoading(false); // notification if (!isNotificationsEnabled()) return; setNotifMsg(msg); setNotifSeverity('error'); setNotifOpen(true); return; } setUploadStatus('loading'); try { await uploadSkin(username, skinFile, skinModel); setStatusMessage('Скин успешно загружен!'); setUploadStatus('success'); // 1) подтягиваем свежий skin_url с бэка const config = JSON.parse(localStorage.getItem('launcher_config') || '{}'); if (config.uuid) { await loadPlayerData(config.uuid); } // 2) сообщаем TopBar'у, что скин обновился window.dispatchEvent(new CustomEvent('skin-updated')); // notification if (!isNotificationsEnabled()) return; setNotifMsg('Скин успешно загружен!'); setNotifSeverity('success'); setNotifPos(getNotifPositionFromSettings()); setNotifOpen(true); } catch (error) { const msg = `Ошибка: ${ error instanceof Error ? error.message : 'Не удалось загрузить скин' }`; setStatusMessage(msg); setUploadStatus('error'); // notification if (!isNotificationsEnabled()) return; setNotifMsg(msg); setNotifSeverity('error'); setNotifPos(getNotifPositionFromSettings()); setNotifOpen(true); } finally { setLoading(false); } }; const GRADIENT = 'linear-gradient(71deg, #F27121 0%, #E940CD 70%, #8A2387 100%)'; useEffect(() => { const STORAGE_KEY = 'launcher_settings'; const read = () => { try { const raw = localStorage.getItem(STORAGE_KEY); const s = raw ? JSON.parse(raw) : null; setAutoRotate(s?.autoRotateSkinViewer ?? true); setWalkingSpeed(s?.walkingSpeed ?? 0.5); } catch { setAutoRotate(true); setWalkingSpeed(0.5); } }; read(); // если хочешь, чтобы обновлялось сразу, когда Settings сохраняют: const onStorage = (e: StorageEvent) => { if (e.key === STORAGE_KEY) read(); }; window.addEventListener('storage', onStorage); // и наш “локальный” евент (для Electron/одного окна storage может не стрелять) const onSettingsUpdated = () => read(); window.addEventListener('settings-updated', onSettingsUpdated as EventListener); return () => { window.removeEventListener('storage', onStorage); window.removeEventListener('settings-updated', onSettingsUpdated as EventListener); }; }, []); return ( setNotifOpen(false)} autoHideDuration={2500} /> {loading ? ( ) : ( {/* LEFT COLUMN */} {/* Плашка с ником */} {username} {/* SkinViewer */} {/* Загрузчик скинов */} {/* dropzone */} { e.preventDefault(); setIsDragOver(true); }} onDragLeave={() => setIsDragOver(false)} onDrop={handleFileDrop} onClick={() => fileInputRef.current?.click()} > {skinFile ? `Выбран файл: ${skinFile.name}` : 'Перетащите PNG файл скина или кликните для выбора'} Только .png • Рекомендуется 64×64 {/* select */} Модель скина {/* button */} {/* RIGHT COLUMN */} {/* Плащи */} Ваши плащи {capes.map((cape) => ( ))} {/* Онлайн */} )} ); }