fix: Fix 3d models
This commit is contained in:
		
							
								
								
									
										263
									
								
								src/widgets/MediaViewer/ThreeViewErrorBoundary.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								src/widgets/MediaViewer/ThreeViewErrorBoundary.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,263 @@ | ||||
| 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<Props, State> { | ||||
|   constructor(props: Props) { | ||||
|     super(props); | ||||
|     this.state = { | ||||
|       hasError: false, | ||||
|       error: null, | ||||
|       lastResetKey: props.resetKey, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   static getDerivedStateFromError(error: Error): Partial<State> { | ||||
|     return { | ||||
|       hasError: true, | ||||
|       error, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   static getDerivedStateFromProps( | ||||
|     props: Props, | ||||
|     state: State | ||||
|   ): Partial<State> | 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) { | ||||
|         console.log( | ||||
|           "🔄 ThreeViewErrorBoundary: Сброс ошибки при смене модели", | ||||
|           { | ||||
|             oldKey: state.lastResetKey, | ||||
|             newKey: props.resetKey, | ||||
|             oldMediaId, | ||||
|             newMediaId, | ||||
|           } | ||||
|         ); | ||||
|         return { | ||||
|           hasError: false, | ||||
|           error: null, | ||||
|           lastResetKey: props.resetKey, | ||||
|         }; | ||||
|       } | ||||
|  | ||||
|       // Если изменился только счетчик (нажата кнопка "Попробовать снова"), обновляем lastResetKey | ||||
|       // но не сбрасываем ошибку автоматически - ждем результата загрузки | ||||
|       console.log( | ||||
|         "🔄 ThreeViewErrorBoundary: Обновление lastResetKey без сброса ошибки", | ||||
|         { | ||||
|           oldKey: state.lastResetKey, | ||||
|           newKey: props.resetKey, | ||||
|         } | ||||
|       ); | ||||
|       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 = () => { | ||||
|     console.log( | ||||
|       "🔄 ThreeViewErrorBoundary: Перезагрузка компонента и перезапрос модели" | ||||
|     ); | ||||
|  | ||||
|     // Сначала сбрасываем состояние ошибки | ||||
|     this.setState( | ||||
|       { | ||||
|         hasError: false, | ||||
|         error: null, | ||||
|       }, | ||||
|       () => { | ||||
|         // После того как состояние обновилось, вызываем callback для изменения resetKey | ||||
|         // Это приведет к пересозданию компонента и новой попытке загрузки | ||||
|         this.props.onReset?.(); | ||||
|       } | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   handleClose = () => { | ||||
|     console.log("❌ ThreeViewErrorBoundary: Закрытие ошибки"); | ||||
|     this.setState({ | ||||
|       hasError: false, | ||||
|       error: null, | ||||
|     }); | ||||
|   }; | ||||
|  | ||||
|   render() { | ||||
|     if (this.state.hasError) { | ||||
|       return ( | ||||
|         <Box | ||||
|           sx={{ | ||||
|             width: "100%", | ||||
|             height: "100%", | ||||
|             display: "flex", | ||||
|             justifyContent: "center", | ||||
|             alignItems: "center", | ||||
|             p: 2, | ||||
|           }} | ||||
|         > | ||||
|           <Paper | ||||
|             elevation={3} | ||||
|             sx={{ | ||||
|               p: 3, | ||||
|               maxWidth: 500, | ||||
|               width: "100%", | ||||
|               position: "relative", | ||||
|               backgroundColor: "error.light", | ||||
|               color: "error.contrastText", | ||||
|             }} | ||||
|           > | ||||
|             <Box sx={{ display: "flex", alignItems: "center", mb: 2 }}> | ||||
|               <AlertTriangle size={32} style={{ marginRight: 12 }} /> | ||||
|               <Typography variant="h6" component="h2"> | ||||
|                 Ошибка загрузки 3D модели | ||||
|               </Typography> | ||||
|             </Box> | ||||
|  | ||||
|             <Typography variant="body2" sx={{ mb: 2 }}> | ||||
|               {this.getErrorMessage()} | ||||
|             </Typography> | ||||
|  | ||||
|             <Typography variant="caption" sx={{ mb: 2, display: "block" }}> | ||||
|               Возможные причины: | ||||
|               <ul style={{ margin: "8px 0", paddingLeft: "20px" }}> | ||||
|                 {this.getErrorReasons().map((reason, index) => ( | ||||
|                   <li key={index}>{reason}</li> | ||||
|                 ))} | ||||
|               </ul> | ||||
|             </Typography> | ||||
|  | ||||
|             {this.state.error?.message && ( | ||||
|               <Typography | ||||
|                 variant="caption" | ||||
|                 sx={{ | ||||
|                   mb: 2, | ||||
|                   display: "block", | ||||
|                   fontFamily: "monospace", | ||||
|                   backgroundColor: "rgba(0, 0, 0, 0.1)", | ||||
|                   p: 1, | ||||
|                   borderRadius: 1, | ||||
|                   fontSize: "0.7rem", | ||||
|                   wordBreak: "break-word", | ||||
|                   maxHeight: "100px", | ||||
|                   overflow: "auto", | ||||
|                 }} | ||||
|               > | ||||
|                 {this.state.error.message} | ||||
|               </Typography> | ||||
|             )} | ||||
|  | ||||
|             <Box sx={{ display: "flex", gap: 1 }}> | ||||
|               <Button | ||||
|                 variant="contained" | ||||
|                 startIcon={<RefreshCw size={16} />} | ||||
|                 onClick={() => { | ||||
|                   console.log( | ||||
|                     "🖱️ ThreeViewErrorBoundary: Клик на кнопку 'Попробовать снова'" | ||||
|                   ); | ||||
|                   this.handleReset(); | ||||
|                 }} | ||||
|                 sx={{ | ||||
|                   backgroundColor: "error.contrastText", | ||||
|                   color: "error.main", | ||||
|                   "&:hover": { | ||||
|                     backgroundColor: "rgba(255, 255, 255, 0.9)", | ||||
|                   }, | ||||
|                 }} | ||||
|               > | ||||
|                 Попробовать снова | ||||
|               </Button> | ||||
|             </Box> | ||||
|           </Paper> | ||||
|         </Box> | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     return this.props.children; | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user