Files
WhiteNightsAdminPanel/src/widgets/MediaViewer/ThreeViewErrorBoundary.tsx

241 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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) {
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 (
<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={() => {
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;
}
}