197 lines
4.6 KiB
TypeScript
197 lines
4.6 KiB
TypeScript
import React from "react";
|
||
import {
|
||
Box,
|
||
Typography,
|
||
LinearProgress,
|
||
CircularProgress,
|
||
} from "@mui/material";
|
||
|
||
interface ModelLoadingIndicatorProps {
|
||
progress?: number;
|
||
message?: string;
|
||
isVisible?: boolean;
|
||
variant?: "overlay" | "inline";
|
||
size?: "small" | "medium" | "large";
|
||
showDetails?: boolean;
|
||
}
|
||
|
||
export const ModelLoadingIndicator: React.FC<ModelLoadingIndicatorProps> = ({
|
||
progress = 0,
|
||
message = "Загрузка 3D модели...",
|
||
isVisible = true,
|
||
variant = "overlay",
|
||
size = "medium",
|
||
showDetails = true,
|
||
}) => {
|
||
const sizeConfig = {
|
||
small: {
|
||
spinnerSize: 32,
|
||
fontSize: "0.75rem",
|
||
progressBarWidth: 150,
|
||
padding: 2,
|
||
},
|
||
medium: {
|
||
spinnerSize: 48,
|
||
fontSize: "0.875rem",
|
||
progressBarWidth: 200,
|
||
padding: 3,
|
||
},
|
||
large: {
|
||
spinnerSize: 64,
|
||
fontSize: "1rem",
|
||
progressBarWidth: 250,
|
||
padding: 4,
|
||
},
|
||
};
|
||
|
||
const currentSize = sizeConfig[size];
|
||
|
||
if (!isVisible) return null;
|
||
|
||
const getProgressStage = (progress: number): string => {
|
||
if (progress < 20) return "Инициализация...";
|
||
if (progress < 40) return "Загрузка геометрии...";
|
||
if (progress < 60) return "Обработка материалов...";
|
||
if (progress < 80) return "Загрузка текстур...";
|
||
if (progress < 95) return "Финализация...";
|
||
if (progress === 100) return "Готово!";
|
||
return "Загрузка...";
|
||
};
|
||
|
||
const content = (
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
gap: 2,
|
||
padding: currentSize.padding,
|
||
textAlign: "center",
|
||
width: "100%",
|
||
}}
|
||
>
|
||
{/* Крутяшка с процентами */}
|
||
<Box sx={{ position: "relative", display: "inline-flex" }}>
|
||
<CircularProgress
|
||
size={currentSize.spinnerSize}
|
||
variant="determinate"
|
||
value={progress}
|
||
thickness={4}
|
||
sx={{
|
||
color: "primary.main",
|
||
}}
|
||
/>
|
||
<Box
|
||
sx={{
|
||
top: 0,
|
||
left: 0,
|
||
bottom: 0,
|
||
right: 0,
|
||
position: "absolute",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
}}
|
||
>
|
||
<Typography
|
||
variant="caption"
|
||
component="div"
|
||
color="text.secondary"
|
||
sx={{
|
||
fontSize: currentSize.spinnerSize * 0.25,
|
||
fontWeight: 600,
|
||
lineHeight: 1,
|
||
}}
|
||
>
|
||
{`${Math.round(progress)}%`}
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
{/* Линейный прогресс бар */}
|
||
<Box sx={{ width: "100%", maxWidth: currentSize.progressBarWidth }}>
|
||
<LinearProgress
|
||
variant="determinate"
|
||
value={progress}
|
||
color="primary"
|
||
sx={{
|
||
height: 8,
|
||
borderRadius: 4,
|
||
backgroundColor: "rgba(0, 0, 0, 0.1)",
|
||
}}
|
||
/>
|
||
</Box>
|
||
|
||
{/* Основное сообщение */}
|
||
<Typography
|
||
variant="body2"
|
||
color="text.secondary"
|
||
sx={{
|
||
fontSize: currentSize.fontSize,
|
||
fontWeight: 500,
|
||
maxWidth: 280,
|
||
lineHeight: 1.4,
|
||
}}
|
||
>
|
||
{message}
|
||
</Typography>
|
||
|
||
{/* Детальная информация о прогрессе */}
|
||
{showDetails && progress > 0 && (
|
||
<Typography
|
||
variant="caption"
|
||
color="text.disabled"
|
||
sx={{
|
||
fontSize: "0.75rem",
|
||
opacity: 0.8,
|
||
fontWeight: 400,
|
||
}}
|
||
>
|
||
{getProgressStage(progress)}
|
||
</Typography>
|
||
)}
|
||
</Box>
|
||
);
|
||
|
||
if (variant === "overlay") {
|
||
return (
|
||
<Box
|
||
sx={{
|
||
position: "absolute",
|
||
top: 0,
|
||
left: 0,
|
||
right: 0,
|
||
bottom: 0,
|
||
backgroundColor: "rgba(255, 255, 255, 0.95)",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
zIndex: 1000,
|
||
borderRadius: 1,
|
||
border: "1px solid rgba(0, 0, 0, 0.05)",
|
||
}}
|
||
>
|
||
{content}
|
||
</Box>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
minHeight: 200,
|
||
backgroundColor: "rgba(0, 0, 0, 0.02)",
|
||
borderRadius: 2,
|
||
border: "1px dashed",
|
||
borderColor: "divider",
|
||
}}
|
||
>
|
||
{content}
|
||
</Box>
|
||
);
|
||
};
|