import React, { Component, ReactNode } from "react"; import { Box, Button, Typography, Paper } from "@mui/material"; import { RefreshCw, AlertTriangle } from "lucide-react"; interface Props { children: ReactNode; onReset?: () => void; resetKey?: number | string; } interface State { hasError: boolean; error: Error | null; lastResetKey?: number | string; } export class ThreeViewErrorBoundary extends Component { constructor(props: Props) { super(props); this.state = { hasError: false, error: null, lastResetKey: props.resetKey, }; } static getDerivedStateFromError(error: Error): Partial { return { hasError: true, error, }; } static getDerivedStateFromProps( props: Props, state: State ): Partial | null { // Сбрасываем ошибку ТОЛЬКО при смене медиа (когда меняется ID в resetKey) if ( props.resetKey !== state.lastResetKey && state.lastResetKey !== undefined ) { const oldMediaId = String(state.lastResetKey).split("-")[0]; const newMediaId = String(props.resetKey).split("-")[0]; // Сбрасываем ошибку только если изменился ID медиа (пользователь выбрал другую модель) if (oldMediaId !== newMediaId) { return { hasError: false, error: null, lastResetKey: props.resetKey, }; } // Если изменился только счетчик (нажата кнопка "Попробовать снова"), обновляем lastResetKey // но не сбрасываем ошибку автоматически - ждем результата загрузки return { lastResetKey: props.resetKey, }; } if (state.lastResetKey === undefined && props.resetKey !== undefined) { return { lastResetKey: props.resetKey, }; } return null; } componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { console.error("❌ ThreeViewErrorBoundary: Ошибка загрузки 3D модели", { error: error.message, stack: error.stack, componentStack: errorInfo.componentStack, }); } getErrorMessage = () => { const errorMessage = this.state.error?.message || ""; if ( errorMessage.includes("not valid JSON") || errorMessage.includes("Unexpected token") ) { return "Неверный формат файла. Убедитесь, что файл является корректной 3D моделью в формате GLB/GLTF."; } if (errorMessage.includes("Could not load")) { return "Не удалось загрузить файл 3D модели. Проверьте, что файл существует и доступен."; } if (errorMessage.includes("404") || errorMessage.includes("Not Found")) { return "Файл 3D модели не найден на сервере."; } if (errorMessage.includes("Network") || errorMessage.includes("fetch")) { return "Ошибка сети при загрузке 3D модели. Проверьте интернет-соединение."; } return ( errorMessage || "Произошла неизвестная ошибка при загрузке 3D модели" ); }; getErrorReasons = () => { const errorMessage = this.state.error?.message || ""; if ( errorMessage.includes("not valid JSON") || errorMessage.includes("Unexpected token") ) { return [ "Файл не является 3D моделью", "Загружен файл неподдерживаемого формата", "Файл поврежден или не полностью загружен", "Используйте только GLB или GLTF форматы", ]; } return [ "Поврежденный файл модели", "Неподдерживаемый формат", "Проблемы с загрузкой файла", ]; }; handleReset = () => { // Сначала сбрасываем состояние ошибки this.setState( { hasError: false, error: null, }, () => { // После того как состояние обновилось, вызываем callback для изменения resetKey // Это приведет к пересозданию компонента и новой попытке загрузки this.props.onReset?.(); } ); }; handleClose = () => { this.setState({ hasError: false, error: null, }); }; render() { if (this.state.hasError) { return ( Ошибка загрузки 3D модели {this.getErrorMessage()} Возможные причины:
    {this.getErrorReasons().map((reason, index) => (
  • {reason}
  • ))}
{this.state.error?.message && ( {this.state.error.message} )}
); } return this.props.children; } }