feat: Sight Page update
This commit is contained in:
259
src/shared/modals/UploadMediaDialog/index.tsx
Normal file
259
src/shared/modals/UploadMediaDialog/index.tsx
Normal file
@@ -0,0 +1,259 @@
|
||||
import { MEDIA_TYPE_LABELS, editSightStore } from "@shared";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
TextField,
|
||||
Paper,
|
||||
Box,
|
||||
CircularProgress,
|
||||
Alert,
|
||||
Snackbar,
|
||||
Select,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
} from "@mui/material";
|
||||
import { Save } from "lucide-react";
|
||||
import { ModelViewer3D } from "@widgets";
|
||||
|
||||
interface UploadMediaDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
afterUpload: (media: {
|
||||
id: string;
|
||||
filename: string;
|
||||
media_name?: string;
|
||||
media_type: number;
|
||||
}) => void;
|
||||
}
|
||||
|
||||
export const UploadMediaDialog = observer(
|
||||
({ open, onClose, afterUpload }: UploadMediaDialogProps) => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [success, setSuccess] = useState(false);
|
||||
const [mediaName, setMediaName] = useState("");
|
||||
const [mediaFilename, setMediaFilename] = useState("");
|
||||
const [mediaType, setMediaType] = useState(0);
|
||||
const [mediaFile, setMediaFile] = useState<File | null>(null);
|
||||
const { fileToUpload, uploadMedia } = editSightStore;
|
||||
const [mediaUrl, setMediaUrl] = useState<string | null>(null);
|
||||
const [availableMediaTypes, setAvailableMediaTypes] = useState<number[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (fileToUpload) {
|
||||
setMediaFile(fileToUpload);
|
||||
setMediaFilename(fileToUpload.name);
|
||||
// Try to determine media type from file extension
|
||||
const extension = fileToUpload.name.split(".").pop()?.toLowerCase();
|
||||
if (extension) {
|
||||
if (["glb", "gltf"].includes(extension)) {
|
||||
setAvailableMediaTypes([6]);
|
||||
setMediaType(6);
|
||||
}
|
||||
if (["jpg", "jpeg", "png", "gif"].includes(extension)) {
|
||||
// Для изображений доступны все типы кроме видео
|
||||
setAvailableMediaTypes([1, 3, 4, 5]); // Фото, Иконка, Водяной знак, Панорама, 3Д-модель
|
||||
setMediaType(1); // По умолчанию Фото
|
||||
} else if (["mp4", "webm", "mov"].includes(extension)) {
|
||||
// Для видео только тип Видео
|
||||
setAvailableMediaTypes([2]);
|
||||
setMediaType(2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [fileToUpload]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mediaFile) {
|
||||
setMediaUrl(URL.createObjectURL(mediaFile as Blob));
|
||||
}
|
||||
}, [mediaFile]);
|
||||
|
||||
// const fileFormat = useEffect(() => {
|
||||
// const handleKeyPress = (event: KeyboardEvent) => {
|
||||
// if (event.key.toLowerCase() === "enter" && !event.ctrlKey) {
|
||||
// event.preventDefault();
|
||||
// onClose();
|
||||
// }
|
||||
// };
|
||||
|
||||
// window.addEventListener("keydown", handleKeyPress);
|
||||
// return () => window.removeEventListener("keydown", handleKeyPress);
|
||||
// }, [onClose]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!mediaFile) return;
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const media = await uploadMedia(
|
||||
mediaFilename,
|
||||
mediaType,
|
||||
mediaFile,
|
||||
mediaName
|
||||
);
|
||||
if (media) {
|
||||
await afterUpload(media);
|
||||
}
|
||||
setSuccess(true);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Failed to save media");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setError(null);
|
||||
setSuccess(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
||||
<DialogTitle>Просмотр медиа</DialogTitle>
|
||||
<DialogContent
|
||||
className="flex gap-4"
|
||||
dividers
|
||||
sx={{
|
||||
height: "600px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 2,
|
||||
pt: 2,
|
||||
}}
|
||||
>
|
||||
<Box className="flex flex-col gap-4">
|
||||
<Box className="flex gap-2">
|
||||
<TextField
|
||||
fullWidth
|
||||
value={mediaName}
|
||||
onChange={(e) => setMediaName(e.target.value)}
|
||||
label="Название медиа"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
value={mediaFilename}
|
||||
onChange={(e) => setMediaFilename(e.target.value)}
|
||||
label="Название файла"
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<FormControl fullWidth sx={{ width: "50%" }}>
|
||||
<InputLabel>Тип медиа</InputLabel>
|
||||
<Select
|
||||
value={mediaType}
|
||||
label="Тип медиа"
|
||||
onChange={(e) => setMediaType(Number(e.target.value))}
|
||||
disabled={isLoading}
|
||||
>
|
||||
{availableMediaTypes.map((type) => (
|
||||
<MenuItem key={type} value={type}>
|
||||
{
|
||||
MEDIA_TYPE_LABELS[
|
||||
type as keyof typeof MEDIA_TYPE_LABELS
|
||||
]
|
||||
}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
||||
<Box className="flex gap-4 h-full">
|
||||
<Paper
|
||||
elevation={2}
|
||||
sx={{
|
||||
flex: 1,
|
||||
p: 2,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
minHeight: 400,
|
||||
}}
|
||||
>
|
||||
{/* <MediaViewer
|
||||
media={{
|
||||
id: "",
|
||||
media_type: mediaType,
|
||||
filename: mediaFilename,
|
||||
}}
|
||||
/> */}
|
||||
{mediaType === 6 && mediaUrl && (
|
||||
<ModelViewer3D fileUrl={mediaUrl} height="100%" />
|
||||
)}
|
||||
{mediaType !== 6 && mediaType !== 2 && mediaUrl && (
|
||||
<img
|
||||
src={mediaUrl ?? ""}
|
||||
alt="Uploaded media"
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
|
||||
<Box className="flex flex-col gap-2 self-end">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={
|
||||
isLoading ? (
|
||||
<CircularProgress size={16} />
|
||||
) : (
|
||||
<Save size={16} />
|
||||
)
|
||||
}
|
||||
onClick={handleSave}
|
||||
disabled={isLoading || (!mediaName && !mediaFilename)}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose} disabled={isLoading}>
|
||||
Отмена
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<Snackbar
|
||||
open={!!error}
|
||||
autoHideDuration={6000}
|
||||
onClose={() => setError(null)}
|
||||
>
|
||||
<Alert severity="error" onClose={() => setError(null)}>
|
||||
{error}
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
|
||||
<Snackbar
|
||||
open={success}
|
||||
autoHideDuration={3000}
|
||||
onClose={() => setSuccess(false)}
|
||||
>
|
||||
<Alert severity="success" onClose={() => setSuccess(false)}>
|
||||
Медиа успешно сохранено
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user