feat: Add more pages
This commit is contained in:
		
							
								
								
									
										295
									
								
								src/pages/Media/MediaEditPage/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								src/pages/Media/MediaEditPage/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,295 @@ | ||||
| import { useEffect, useState, useRef } from "react"; | ||||
| import { observer } from "mobx-react-lite"; | ||||
| import { useNavigate, useParams } from "react-router-dom"; | ||||
| import { | ||||
|   Box, | ||||
|   Button, | ||||
|   CircularProgress, | ||||
|   FormControl, | ||||
|   InputLabel, | ||||
|   MenuItem, | ||||
|   Paper, | ||||
|   Select, | ||||
|   TextField, | ||||
|   Typography, | ||||
|   Alert, | ||||
|   Snackbar, | ||||
| } from "@mui/material"; | ||||
| import { Save, ArrowLeft } from "lucide-react"; | ||||
| import { authInstance, mediaStore, MEDIA_TYPE_LABELS } from "@shared"; | ||||
| import { MediaViewer } from "@widgets"; | ||||
|  | ||||
| export const MediaEditPage = observer(() => { | ||||
|   const { id } = useParams<{ id: string }>(); | ||||
|   const navigate = useNavigate(); | ||||
|   const [isLoading, setIsLoading] = useState(false); | ||||
|   const [error, setError] = useState<string | null>(null); | ||||
|   const [success, setSuccess] = useState(false); | ||||
|  | ||||
|   const fileInputRef = useRef<HTMLInputElement>(null); | ||||
|   const [newFile, setNewFile] = useState<File | null>(null); | ||||
|   const [uploadDialogOpen, setUploadDialogOpen] = useState(false); // State for the upload dialog | ||||
|  | ||||
|   const media = id ? mediaStore.media.find((m) => m.id === id) : null; | ||||
|   const [mediaName, setMediaName] = useState(media?.media_name ?? ""); | ||||
|   const [mediaFilename, setMediaFilename] = useState(media?.filename ?? ""); | ||||
|   const [mediaType, setMediaType] = useState(media?.media_type ?? 1); | ||||
|   const [availableMediaTypes, setAvailableMediaTypes] = useState<number[]>([]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (id) { | ||||
|       mediaStore.getOneMedia(id); | ||||
|     } | ||||
|     console.log(newFile); | ||||
|     console.log(uploadDialogOpen); | ||||
|   }, [id]); | ||||
|  | ||||
|   useEffect(() => { | ||||
|     if (media) { | ||||
|       setMediaName(media.media_name); | ||||
|       setMediaFilename(media.filename); | ||||
|       setMediaType(media.media_type); | ||||
|  | ||||
|       // Set available media types based on current file extension | ||||
|       const extension = media.filename.split(".").pop()?.toLowerCase(); | ||||
|       if (extension) { | ||||
|         if (["glb", "gltf"].includes(extension)) { | ||||
|           setAvailableMediaTypes([6]); // 3D model | ||||
|         } else if (["jpg", "jpeg", "png", "gif"].includes(extension)) { | ||||
|           setAvailableMediaTypes([1, 3, 4, 5]); // Photo, Icon, Watermark, Panorama | ||||
|         } else if (["mp4", "webm", "mov"].includes(extension)) { | ||||
|           setAvailableMediaTypes([2]); // Video | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }, [media]); | ||||
|  | ||||
|   // const handleDrop = (e: DragEvent<HTMLDivElement>) => { | ||||
|   //   e.preventDefault(); | ||||
|   //   e.stopPropagation(); | ||||
|   //   setIsDragging(false); | ||||
|  | ||||
|   //   const files = Array.from(e.dataTransfer.files); | ||||
|   //   if (files.length > 0) { | ||||
|   //     setNewFile(files[0]); | ||||
|   //     setMediaFilename(files[0].name); | ||||
|   //     setUploadDialogOpen(true); // Open dialog on file drop | ||||
|   //   } | ||||
|   // }; | ||||
|  | ||||
|   // const handleDragOver = (e: DragEvent<HTMLDivElement>) => { | ||||
|   //   e.preventDefault(); | ||||
|   //   setIsDragging(true); | ||||
|   // }; | ||||
|  | ||||
|   // const handleDragLeave = () => { | ||||
|   //   setIsDragging(false); | ||||
|   // }; | ||||
|  | ||||
|   const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => { | ||||
|     const files = e.target.files; | ||||
|     if (files && files.length > 0) { | ||||
|       const file = files[0]; | ||||
|       setNewFile(file); | ||||
|       setMediaFilename(file.name); | ||||
|  | ||||
|       // Determine media type based on file extension | ||||
|       const extension = file.name.split(".").pop()?.toLowerCase(); | ||||
|       if (extension) { | ||||
|         if (["glb", "gltf"].includes(extension)) { | ||||
|           setAvailableMediaTypes([6]); // 3D model | ||||
|           setMediaType(6); | ||||
|         } else if (["jpg", "jpeg", "png", "gif"].includes(extension)) { | ||||
|           setAvailableMediaTypes([1, 3, 4, 5]); // Photo, Icon, Watermark, Panorama | ||||
|           setMediaType(1); // Default to Photo | ||||
|         } else if (["mp4", "webm", "mov"].includes(extension)) { | ||||
|           setAvailableMediaTypes([2]); // Video | ||||
|           setMediaType(2); | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       setUploadDialogOpen(true); // Open dialog on file selection | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const handleSave = async () => { | ||||
|     if (!id) return; | ||||
|  | ||||
|     setIsLoading(true); | ||||
|     setError(null); | ||||
|  | ||||
|     try { | ||||
|       await authInstance.patch(`/media/${id}`, { | ||||
|         media_name: mediaName, | ||||
|         filename: mediaFilename, | ||||
|         type: mediaType, | ||||
|       }); | ||||
|  | ||||
|       // If a new file was selected, the actual file upload will happen | ||||
|       // via the UploadMediaDialog. We just need to make sure the metadata | ||||
|       // is updated correctly before or after. | ||||
|       // Since the dialog handles the actual upload, we don't call updateMediaFile here. | ||||
|  | ||||
|       setSuccess(true); | ||||
|       handleUploadSuccess(); | ||||
|     } catch (err) { | ||||
|       setError(err instanceof Error ? err.message : "Failed to save media"); | ||||
|     } finally { | ||||
|       setIsLoading(false); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   const handleUploadSuccess = () => { | ||||
|     // After successful upload in the dialog, refresh media data if needed | ||||
|     if (id) { | ||||
|       mediaStore.getOneMedia(id); | ||||
|     } | ||||
|     setNewFile(null); // Clear the new file state after successful upload | ||||
|     setUploadDialogOpen(false); | ||||
|     setSuccess(true); | ||||
|   }; | ||||
|  | ||||
|   if (!media && id) { | ||||
|     // Only show loading if an ID is present and media is not yet loaded | ||||
|     return ( | ||||
|       <Box className="flex justify-center items-center h-screen"> | ||||
|         <CircularProgress /> | ||||
|       </Box> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <Box className="p-6 max-w-4xl mx-auto"> | ||||
|       <Box className="flex items-center gap-4 mb-6"> | ||||
|         <Button | ||||
|           variant="outlined" | ||||
|           startIcon={<ArrowLeft size={20} />} | ||||
|           onClick={() => navigate("/media")} | ||||
|         > | ||||
|           Назад | ||||
|         </Button> | ||||
|         <Typography variant="h5">Редактирование медиа</Typography> | ||||
|       </Box> | ||||
|  | ||||
|       <Paper className="p-6"> | ||||
|         <input | ||||
|           type="file" | ||||
|           ref={fileInputRef} | ||||
|           className="hidden" | ||||
|           onChange={handleFileSelect} | ||||
|           accept="image/*,video/*,.glb,.gltf" | ||||
|         /> | ||||
|         <Box className="flex flex-col gap-6"> | ||||
|           <Box className="flex gap-4"> | ||||
|             <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.length > 0 | ||||
|                 ? availableMediaTypes.map((type) => ( | ||||
|                     <MenuItem key={type} value={type}> | ||||
|                       { | ||||
|                         MEDIA_TYPE_LABELS[ | ||||
|                           type as keyof typeof MEDIA_TYPE_LABELS | ||||
|                         ] | ||||
|                       } | ||||
|                     </MenuItem> | ||||
|                   )) | ||||
|                 : Object.entries(MEDIA_TYPE_LABELS).map(([type, label]) => ( | ||||
|                     <MenuItem key={type} value={Number(type)}> | ||||
|                       {label} | ||||
|                     </MenuItem> | ||||
|                   ))} | ||||
|             </Select> | ||||
|           </FormControl> | ||||
|  | ||||
|           <Box className="flex gap-6"> | ||||
|             <Paper | ||||
|               elevation={2} | ||||
|               sx={{ | ||||
|                 flex: 1, | ||||
|                 p: 2, | ||||
|                 display: "flex", | ||||
|                 alignItems: "center", | ||||
|                 justifyContent: "center", | ||||
|                 minHeight: 400, | ||||
|               }} | ||||
|             > | ||||
|               <MediaViewer | ||||
|                 media={{ | ||||
|                   id: id || "", | ||||
|                   media_type: mediaType, | ||||
|                   filename: mediaFilename, | ||||
|                 }} | ||||
|               /> | ||||
|             </Paper> | ||||
|  | ||||
|             <Box className="flex flex-col gap-4 self-end"> | ||||
|               <Button | ||||
|                 variant="contained" | ||||
|                 color="primary" | ||||
|                 startIcon={<Save size={20} />} | ||||
|                 onClick={handleSave} | ||||
|                 disabled={isLoading || (!mediaName && !mediaFilename)} | ||||
|               > | ||||
|                 {isLoading ? <CircularProgress size={20} /> : "Сохранить"} | ||||
|               </Button> | ||||
|               {/* Only show "Replace file" button if no new file is currently selected */} | ||||
|             </Box> | ||||
|           </Box> | ||||
|  | ||||
|           {error && ( | ||||
|             <Typography color="error" className="mt-2"> | ||||
|               {error} | ||||
|             </Typography> | ||||
|           )} | ||||
|           {success && ( | ||||
|             <Typography color="success.main" className="mt-2"> | ||||
|               Медиа успешно сохранено | ||||
|             </Typography> | ||||
|           )} | ||||
|         </Box> | ||||
|       </Paper> | ||||
|  | ||||
|       <Snackbar | ||||
|         open={!!error} | ||||
|         autoHideDuration={6000} | ||||
|         onClose={() => setError(null)} | ||||
|       > | ||||
|         <Alert severity="error" onClose={() => setError(null)}> | ||||
|           {error} | ||||
|         </Alert> | ||||
|       </Snackbar> | ||||
|  | ||||
|       <Snackbar | ||||
|         open={success} | ||||
|         autoHideDuration={2000} | ||||
|         onClose={() => setSuccess(false)} | ||||
|       > | ||||
|         <Alert severity="success" onClose={() => setSuccess(false)}> | ||||
|           Медиа успешно сохранено | ||||
|         </Alert> | ||||
|       </Snackbar> | ||||
|     </Box> | ||||
|   ); | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user