Files
WhiteNightsAdminPanel/src/widgets/MediaViewer/ThreeViewErrorBoundary.tsx
2025-11-06 00:58:10 +03:00

233 lines
6.4 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 {
if (
props.resetKey !== state.lastResetKey &&
state.lastResetKey !== undefined
) {
const oldMediaId = String(state.lastResetKey).split("-")[0];
const newMediaId = String(props.resetKey).split("-")[0];
if (oldMediaId !== newMediaId) {
return {
hasError: false,
error: null,
lastResetKey: 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 = () => {
this.setState(
{
hasError: false,
error: null,
},
() => {
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;
}
}