rewrite code with edit article in sight page

This commit is contained in:
Илья Куприец 2025-05-01 00:10:50 +03:00
parent dc483d62de
commit 65532f7074
5 changed files with 880 additions and 322 deletions

View File

@ -33,6 +33,7 @@ type Props = {
parentResource: string;
childResource: string;
title: string;
left?: boolean;
};
export const CreateSightArticle = ({
@ -40,6 +41,7 @@ export const CreateSightArticle = ({
parentResource,
childResource,
title,
left,
}: Props) => {
const theme = useTheme();
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
@ -118,7 +120,8 @@ export const CreateSightArticle = ({
const existingItems = existingItemsResponse.data || [];
const nextPageNum = existingItems.length + 1;
// Привязываем статью к достопримечательности
if (!left) {
// Привязываем статью к достопримечательности если она не левая
await axiosInstance.post(
`${
import.meta.env.VITE_KRBL_API
@ -128,6 +131,7 @@ export const CreateSightArticle = ({
page_num: nextPageNum,
}
);
}
// Загружаем все медиа файлы и получаем их ID
const mediaIds = await Promise.all(
@ -174,6 +178,7 @@ export const CreateSightArticle = ({
marginTop: 2,
background: theme.palette.background.paper,
borderBottom: `1px solid ${theme.palette.divider}`,
zIndex: 2000,
}}
>
<Typography variant="subtitle1" fontWeight="bold">
@ -192,6 +197,10 @@ export const CreateSightArticle = ({
fullWidth
InputLabelProps={{ shrink: true }}
type="text"
sx={{
zIndex: 2000,
backgroundColor: theme.palette.background.paper,
}}
label="Заголовок *"
/>

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { Close } from "@mui/icons-material";
import { languageStore } from "../store/LanguageStore";
import {
Stack,
Typography,
@ -88,6 +88,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
type,
onSave,
}: LinkedItemsProps<T>) => {
const { language } = languageStore;
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
const { setStationModalOpenAction, setStationIdAction } = stationStore;
const [position, setPosition] = useState<number>(1);
@ -152,7 +153,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
setLinkedItems([]);
});
}
}, [parentId, parentResource, childResource]);
}, [parentId, parentResource, childResource, language]);
useEffect(() => {
if (type === "edit") {
@ -354,7 +355,10 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
variant="outlined"
color="error"
size="small"
onClick={() => deleteItem(item.id)}
onClick={(e) => {
e.stopPropagation();
deleteItem(item.id);
}}
>
Отвязать
</Button>
@ -430,7 +434,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
<FormControl fullWidth>
<TextField
type="number"
label="Номер страницы"
label="Позиция добавляемой статьи"
name="page_num"
value={pageNum}
onChange={(e) => {

View File

@ -4,16 +4,30 @@ import { observer } from "mobx-react-lite";
import { useForm } from "@refinedev/react-hook-form";
import { Controller } from "react-hook-form";
import "easymde/dist/easymde.min.css";
import { memo, useMemo, useEffect } from "react";
import { memo, useMemo, useEffect, useCallback } from "react";
import { MarkdownEditor } from "../../MarkdownEditor";
import { Edit } from "@refinedev/mui";
import { languageStore } from "../../../store/LanguageStore";
import { LanguageSwitch } from "../../LanguageSwitch/index";
import { useNavigate } from "react-router";
import { useState } from "react";
import { useDropzone } from "react-dropzone";
import {
ALLOWED_IMAGE_TYPES,
ALLOWED_VIDEO_TYPES,
} from "../../media/MediaFormUtils";
import { axiosInstance } from "../../../providers/data";
import { TOKEN_KEY } from "../../../authProvider";
const MemoizedSimpleMDE = memo(MarkdownEditor);
type MediaFile = {
file: File;
preview: string;
uploading: boolean;
mediaId?: number;
};
const style = {
position: "absolute",
top: "50%",
@ -45,12 +59,58 @@ export const ArticleEditModal = observer(() => {
articleStore;
const { language } = languageStore;
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
useEffect(() => {
return () => {
setArticleModalOpenAction(false);
};
}, []);
// Load existing media files when editing an article
useEffect(() => {
const loadExistingMedia = async () => {
if (selectedArticleId) {
try {
const response = await axiosInstance.get(
`${
import.meta.env.VITE_KRBL_API
}/article/${selectedArticleId}/media`
);
const existingMedia = response.data;
// Convert existing media to MediaFile format
const mediaFiles = await Promise.all(
existingMedia.map(async (media: any) => {
const response = await fetch(
`${import.meta.env.VITE_KRBL_MEDIA}${
media.id
}/download?token=${localStorage.getItem(TOKEN_KEY)}`
);
const blob = await response.blob();
const file = new File([blob], media.filename, {
type: media.media_type === 1 ? "image/jpeg" : "video/mp4",
});
return {
file,
preview: URL.createObjectURL(blob),
uploading: false,
mediaId: media.id,
};
})
);
setMediaFiles(mediaFiles);
} catch (error) {
console.error("Error loading existing media:", error);
}
}
};
loadExistingMedia();
}, [selectedArticleId]);
const {
register,
control,
@ -66,10 +126,37 @@ export const ArticleEditModal = observer(() => {
action: "edit",
redirect: false,
onMutationSuccess: () => {
onMutationSuccess: async () => {
try {
// Upload new media files
const newMediaFiles = mediaFiles.filter((file) => !file.mediaId);
const mediaIds = await Promise.all(
newMediaFiles.map(async (mediaFile) => {
return await uploadMedia(mediaFile);
})
);
// Associate all media with the article
await Promise.all(
mediaIds.map((mediaId, index) =>
axiosInstance.post(
`${
import.meta.env.VITE_KRBL_API
}/article/${selectedArticleId}/media/`,
{
media_id: mediaId,
media_order: index + 1,
}
)
)
);
setArticleModalOpenAction(false);
reset();
window.location.reload();
} catch (error) {
console.error("Error handling media:", error);
}
},
meta: {
headers: {
@ -112,6 +199,65 @@ export const ArticleEditModal = observer(() => {
[]
);
const onDrop = useCallback((acceptedFiles: File[]) => {
const newFiles = acceptedFiles.map((file) => ({
file,
preview: URL.createObjectURL(file),
uploading: false,
}));
setMediaFiles((prev) => [...prev, ...newFiles]);
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
"image/*": ALLOWED_IMAGE_TYPES,
"video/*": ALLOWED_VIDEO_TYPES,
},
multiple: true,
});
const uploadMedia = async (mediaFile: MediaFile) => {
const formData = new FormData();
formData.append("media_name", mediaFile.file.name);
formData.append("filename", mediaFile.file.name);
formData.append(
"type",
mediaFile.file.type.startsWith("image/") ? "1" : "2"
);
formData.append("file", mediaFile.file);
const response = await axiosInstance.post(
`${import.meta.env.VITE_KRBL_API}/media`,
formData
);
return response.data.id;
};
const removeMedia = async (index: number) => {
const mediaFile = mediaFiles[index];
// If it's an existing media file (has mediaId), delete it from the server
if (mediaFile.mediaId) {
try {
await axiosInstance.delete(
`${import.meta.env.VITE_KRBL_API}/media/${mediaFile.mediaId}`
);
} catch (error) {
console.error("Error deleting media:", error);
return; // Don't remove from UI if server deletion failed
}
}
// Remove from UI and cleanup
setMediaFiles((prev) => {
const newFiles = [...prev];
URL.revokeObjectURL(newFiles[index].preview);
newFiles.splice(index, 1);
return newFiles;
});
};
return (
<Modal
open={articleModalOpen}
@ -164,6 +310,88 @@ export const ArticleEditModal = observer(() => {
)}
/>
</Box>
{/* Dropzone для медиа файлов */}
<Box sx={{ mt: 2, mb: 2 }}>
<Box
{...getRootProps()}
sx={{
border: "2px dashed",
borderColor: isDragActive ? "primary.main" : "grey.300",
borderRadius: 1,
p: 2,
textAlign: "center",
cursor: "pointer",
"&:hover": {
borderColor: "primary.main",
},
}}
>
<input {...getInputProps()} />
<Typography>
{isDragActive
? "Перетащите файлы сюда..."
: "Перетащите файлы сюда или кликните для выбора"}
</Typography>
</Box>
{/* Превью загруженных файлов */}
<Box sx={{ mt: 2, display: "flex", flexWrap: "wrap", gap: 1 }}>
{mediaFiles.map((mediaFile, index) => (
<Box
key={mediaFile.preview}
sx={{
position: "relative",
width: 100,
height: 100,
}}
>
{mediaFile.file.type.startsWith("image/") ? (
<img
src={mediaFile.preview}
alt={mediaFile.file.name}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
}}
/>
) : (
<Box
sx={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
bgcolor: "grey.200",
}}
>
<Typography variant="caption">
{mediaFile.file.name}
</Typography>
</Box>
)}
<Button
size="small"
color="error"
onClick={() => removeMedia(index)}
sx={{
position: "absolute",
top: 0,
right: 0,
minWidth: "auto",
width: 20,
height: 20,
p: 0,
}}
>
×
</Button>
</Box>
))}
</Box>
</Box>
</Edit>
</Box>
</Modal>

View File

@ -6,11 +6,11 @@ type ModelViewerProps = {
height?: string;
};
export const ModelViewer = ({ fileUrl, height }: ModelViewerProps) => {
export const ModelViewer = ({ fileUrl, height = "80vh" }: ModelViewerProps) => {
const { scene } = useGLTF(fileUrl);
return (
<Canvas style={{ width: "100%", height: "400px" }}>
<Canvas style={{ width: "100%", height: height }}>
<ambientLight />
<directionalLight />
<Stage environment="city" intensity={0.6}>

View File

@ -6,6 +6,7 @@ import {
Typography,
Tab,
Tabs,
Button,
} from "@mui/material";
import { Edit, useAutocomplete } from "@refinedev/mui";
import { useForm } from "@refinedev/react-hook-form";
@ -23,6 +24,8 @@ import axios from "axios";
import { LanguageSwitch } from "../../components/LanguageSwitch/index";
import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer";
import { ModelViewer } from "../media/ModelViewer";
import { articleStore } from "../../store/ArticleStore";
import { ArticleEditModal } from "../../components/modals/ArticleEditModal/index";
function a11yProps(index: number) {
return {
@ -56,7 +59,8 @@ function CustomTabPanel(props: TabPanelProps) {
export const SightEdit = observer(() => {
const { id: sightId } = useParams<{ id: string }>();
const { language, setLanguageAction } = languageStore;
const [previewSelected, setPreviewSelected] = useState(true);
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
const [sightData, setSightData] = useState({
ru: {
name: "",
@ -129,13 +133,16 @@ export const SightEdit = observer(() => {
value,
},
],
meta: {
headers: {
"Accept-Language": language,
},
},
});
const { autocompleteProps: articleAutocompleteProps } = useAutocomplete({
resource: "article",
queryOptions: {
queryKey: ["article", language],
},
onSearch: (value) => [
{
field: "heading",
@ -148,6 +155,12 @@ export const SightEdit = observer(() => {
value,
},
],
meta: {
headers: {
"Accept-Language": language,
},
},
});
useEffect(() => {
@ -189,7 +202,7 @@ export const SightEdit = observer(() => {
latitude: "",
longitude: "",
});
const [selectedArticleIndex, setSelectedArticleIndex] = useState(0);
const [selectedArticleIndex, setSelectedArticleIndex] = useState(-1);
const [cityPreview, setCityPreview] = useState("");
const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null);
const [watermarkLUPreview, setWatermarkLUPreview] = useState<string | null>(
@ -198,11 +211,64 @@ export const SightEdit = observer(() => {
const [watermarkRDPreview, setWatermarkRDPreview] = useState<string | null>(
null
);
const [leftArticlePreview, setLeftArticlePreview] = useState("");
const [previewArticlePreview, setPreviewArticlePreview] = useState("");
const [linkedArticles, setLinkedArticles] = useState<ArticleItem[]>([]);
// Следим за изменениями во всех полях
const selectedArticle = linkedArticles[selectedArticleIndex];
const [previewMedia, setPreviewMedia] = useState<{
src: string;
media_type: number;
filename: string;
} | null>(null);
const [leftArticleMedia, setLeftArticleMedia] = useState<{
src: string;
media_type: number;
filename: string;
} | null>(null);
const previewMediaId = watch("preview_media");
const leftArticleId = watch("left_article");
useEffect(() => {
if (previewMediaId) {
const getMedia = async () => {
try {
const response = await axios.get(
`${import.meta.env.VITE_KRBL_API}/article/${previewMediaId}/media`,
{
headers: {
Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY)}`,
"Accept-Language": language,
},
}
);
const media = response.data[0];
if (media) {
setPreviewMedia({
src: `${import.meta.env.VITE_KRBL_MEDIA}${
media.id
}/download?token=${localStorage.getItem(TOKEN_KEY)}`,
media_type: media.media_type,
filename: media.filename,
});
} else {
setPreviewMedia({
src: "",
media_type: 1,
filename: "",
}); // или другой дефолт
}
} catch (error) {
console.error("Error fetching media:", error);
setPreviewMedia({
src: "",
media_type: 1,
filename: "",
}); // или другой дефолт
}
};
getMedia();
}
}, [previewMediaId]);
const addressContent = watch("address");
const nameContent = watch("name");
@ -244,6 +310,7 @@ export const SightEdit = observer(() => {
{
headers: {
Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY)}`,
"Accept-Language": language,
},
}
);
@ -322,18 +389,36 @@ export const SightEdit = observer(() => {
}, [watermarkRDContent, mediaAutocompleteProps.options]);
useEffect(() => {
const getMedia = async () => {
const selectedLeftArticle = articleAutocompleteProps.options.find(
(option) => option.id === leftArticleContent
);
setLeftArticlePreview(selectedLeftArticle?.heading || "");
}, [leftArticleContent, articleAutocompleteProps.options]);
useEffect(() => {
const selectedPreviewArticle = articleAutocompleteProps.options.find(
(option) => option.id === previewArticleContent
if (!selectedLeftArticle) return;
const response = await axios.get(
`${import.meta.env.VITE_KRBL_API}/article/${
selectedLeftArticle?.id
}/media`,
{
headers: {
Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY)}`,
"Accept-Language": language,
},
}
);
setPreviewArticlePreview(selectedPreviewArticle?.heading || "");
}, [previewArticleContent, articleAutocompleteProps.options]);
const media = response.data[0];
if (media) {
setLeftArticleMedia({
src: `${import.meta.env.VITE_KRBL_MEDIA}${
media.id
}/download?token=${localStorage.getItem(TOKEN_KEY)}`,
media_type: media.media_type,
filename: media.filename,
});
}
};
getMedia();
}, [leftArticleId, leftArticleContent]);
const handleLanguageChange = (lang: string) => {
setSightData((prevData) => ({
@ -567,6 +652,7 @@ export const SightEdit = observer(() => {
)}
/>
<Box sx={{ display: "none" }}>
<Controller
control={control}
name="thumbnail"
@ -611,7 +697,52 @@ export const SightEdit = observer(() => {
/>
)}
/>
</Box>
<Box sx={{ display: "none" }}>
<Controller
control={control}
name="preview_media"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...mediaAutocompleteProps}
value={
mediaAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.media_name : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
option.media_name
.toLowerCase()
.includes(inputValue.toLowerCase())
);
}}
renderInput={(params) => (
<TextField
{...params}
label="Выберите водный знак (Левый верх)"
margin="normal"
variant="outlined"
error={!!errors.arms}
helperText={(errors as any)?.arms?.message}
required
/>
)}
/>
)}
/>
</Box>
<Box sx={{ display: "none" }}>
<Controller
control={control}
@ -715,6 +846,7 @@ export const SightEdit = observer(() => {
}
onChange={(_, value) => {
field.onChange(value?.id || "");
setLeftArticleMedia(null);
}}
getOptionLabel={(item) => {
return item ? item.heading : "";
@ -743,49 +875,6 @@ export const SightEdit = observer(() => {
/>
)}
/>
<Controller
control={control}
name="preview_article"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...articleAutocompleteProps}
value={
articleAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.heading : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
option.heading
.toLowerCase()
.includes(inputValue.toLowerCase())
);
}}
renderInput={(params) => (
<TextField
{...params}
label="Медиа-предпросмотр"
margin="normal"
variant="outlined"
error={!!errors.arms}
helperText={(errors as any)?.arms?.message}
required
/>
)}
/>
)}
/>
</Box>
</Box>
@ -797,20 +886,99 @@ export const SightEdit = observer(() => {
width: "30%",
top: "179px",
minHeight: "600px",
right: 50,
zIndex: 1000,
borderRadius: 2,
border: "1px solid",
borderColor: "primary.main",
bgcolor: (theme) =>
theme.palette.mode === "dark" ? "background.paper" : "#fff",
bgcolor: "#806c59",
}}
>
<Typography variant="h6" gutterBottom color="primary">
Предпросмотр
</Typography>
<Box
sx={{
mb: 2,
margin: "0 auto 40px auto",
display: "flex",
flexDirection: "column",
maxHeight: "300px",
gap: 2,
}}
>
{leftArticleMedia &&
leftArticleMedia.src &&
leftArticleMedia.media_type === 1 && (
<img
src={leftArticleMedia.src}
alt={leftArticleMedia.filename}
style={{
maxWidth: "100%",
height: "300px",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{leftArticleMedia && leftArticleMedia.media_type === 2 && (
<video
src={leftArticleMedia.src}
style={{
maxWidth: "50%",
objectFit: "contain",
borderRadius: 30,
}}
controls
autoPlay
muted
/>
)}
{leftArticleMedia && leftArticleMedia.media_type === 3 && (
<img
src={leftArticleMedia.src}
alt={leftArticleMedia.filename}
style={{
maxWidth: "100%",
height: "40vh",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{leftArticleMedia && leftArticleMedia.media_type === 4 && (
<img
src={leftArticleMedia.src}
alt={leftArticleMedia.filename}
style={{
maxWidth: "100%",
height: "40vh",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{leftArticleMedia &&
leftArticleMedia.src &&
leftArticleMedia.media_type == 5 && (
<ReactPhotoSphereViewer
src={leftArticleMedia.src}
height={"300px"}
width={"350px"}
/>
)}
{leftArticleMedia &&
leftArticleMedia.src &&
leftArticleMedia.media_type === 6 && (
<ModelViewer
height={"400px"}
fileUrl={leftArticleMedia.src}
/>
)}
</Box>
{/* Название достопримечательности */}
<Typography
variant="h4"
@ -855,34 +1023,34 @@ export const SightEdit = observer(() => {
{addressContent}
</Box>
</Typography>
{/* Обложка */}
{thumbnailPreview && (
<Box sx={{ mb: 2 }}>
<Typography
variant="body1"
gutterBottom
sx={{ color: "text.secondary" }}
>
Логотип достопримечательности:
</Typography>
<Box
component="img"
src={thumbnailPreview}
alt="Логотип"
sx={{
maxWidth: "100%",
height: "40vh",
borderRadius: 2,
border: "1px solid",
borderColor: "primary.main",
}}
/>
</Box>
)}
</Paper>
</Box>
</Edit>
{!leftArticleId && (
<CreateSightArticle
parentId={sightId!}
parentResource="sight"
childResource="article"
title="левую статью"
/>
)}
{leftArticleId && (
<Button
variant="outlined"
size="large"
sx={{
marginTop: 5,
width: "100%",
}}
onClick={() => {
setArticleModalOpenAction(true);
setArticleIdAction(leftArticleId);
}}
color="secondary"
>
Редактировать левую статью
</Button>
)}
</CustomTabPanel>
<CustomTabPanel value={tabValue} index={1}>
@ -909,18 +1077,52 @@ export const SightEdit = observer(() => {
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
autoComplete="off"
>
<Controller
control={control}
name="preview_media"
defaultValue={null}
render={({ field }) => (
<Autocomplete
{...articleAutocompleteProps}
value={
articleAutocompleteProps.options.find(
(option) => option.id === field.value
) || null
}
onClick={() => {}}
onChange={(_, value) => {
field.onChange(value?.id || "");
}}
getOptionLabel={(item) => {
return item ? item.heading : "";
}}
isOptionEqualToValue={(option, value) => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
option.heading
.toLowerCase()
.includes(inputValue.toLowerCase())
);
}}
renderInput={(params) => (
<TextField
{...register("name", {
required: "Это поле является обязательным",
})}
error={!!(errors as any)?.name}
helperText={(errors as any)?.name?.message}
{...params}
onClick={() => {
setPreviewSelected(true);
setSelectedArticleIndex(-1);
}}
label="Медиа-предпросмотр"
margin="normal"
fullWidth
InputLabelProps={{ shrink: true }}
type="text"
label={"Название *"}
name="name"
variant="outlined"
error={!!errors.arms}
helperText={(errors as any)?.arms?.message}
required
/>
)}
/>
)}
/>
</Box>
<LanguageSwitch />
@ -941,6 +1143,7 @@ export const SightEdit = observer(() => {
parentResource="sight"
childResource="article"
title="статью"
left
/>
</Box>
</Box>
@ -951,33 +1154,45 @@ export const SightEdit = observer(() => {
flexDirection: "column",
position: "fixed",
p: 2,
height: "max-content",
width: "30%",
overflowY: "scroll",
height: "80vh",
top: "178px",
top: "178px",
minHeight: "600px",
right: 50,
zIndex: 1000,
borderRadius: 2,
border: "1px solid",
borderColor: "primary.main",
bgcolor: (theme) =>
theme.palette.mode === "dark" ? "background.paper" : "#fff",
bgcolor: "#806c59",
}}
>
<Box
sx={{
display: "flex",
flexDirection: "column",
flexGrow: 1,
gap: 2,
}}
>
{!previewSelected && (
<Box
sx={{
mb: 2,
margin: "0 auto",
display: "flex",
flexDirection: "column",
maxHeight: "300px",
gap: 2,
}}
>
<Typography variant="h6" gutterBottom color="primary">
Предпросмотр
</Typography>
<Box sx={{ mb: 2, margin: "0 auto" }}>
{mediaFile && mediaFile.src && mediaFile.media_type === 1 && (
<img
src={mediaFile.src}
alt={mediaFile.filename}
style={{
maxWidth: "100%",
height: "40vh",
height: "300px",
objectFit: "contain",
borderRadius: 8,
}}
@ -1026,26 +1241,117 @@ export const SightEdit = observer(() => {
{mediaFile && mediaFile.src && mediaFile.media_type == 5 && (
<ReactPhotoSphereViewer
src={mediaFile.src}
width={"100%"}
height={"300px"}
width={"350px"}
/>
)}
{mediaFile && mediaFile.media_type === 6 && (
<ModelViewer height={"30%"} fileUrl={mediaFile.src} />
<ModelViewer height={"400px"} fileUrl={mediaFile.src} />
)}
</Box>
{/* Водяные знаки */}
)}
{
<Box
sx={{
mt: 2,
mb: 2,
flexGrow: 1,
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
height: "250px",
overflowY: "scroll",
}}
>
{previewSelected &&
previewMedia &&
previewMedia.src &&
previewMedia.media_type === 1 && (
<img
src={previewMedia.src}
alt={previewMedia.filename}
style={{
maxWidth: "100%",
height: "250px",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{previewSelected &&
previewMedia &&
previewMedia.media_type === 2 && (
<video
src={previewMedia.src}
style={{
maxWidth: "50%",
objectFit: "contain",
borderRadius: 30,
}}
controls
autoPlay
muted
/>
)}
{previewSelected &&
previewMedia &&
previewMedia.media_type === 3 && (
<img
src={previewMedia.src}
alt={previewMedia.filename}
style={{
maxWidth: "100%",
height: "250px",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{previewSelected &&
previewMedia &&
previewMedia.media_type === 4 && (
<img
src={previewMedia.src}
alt={previewMedia.filename}
style={{
maxWidth: "100%",
height: "40vh",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{previewSelected &&
previewMedia &&
previewMedia.src &&
previewMedia.media_type == 5 && (
<ReactPhotoSphereViewer
src={previewMedia.src}
height={"300px"}
width={"350px"}
/>
)}
{previewSelected &&
previewMedia &&
previewMedia.media_type === 6 && (
<ModelViewer height={"400px"} fileUrl={previewMedia.src} />
)}
{!previewSelected && (
<Box
sx={{
display: "flex",
flexDirection: "column",
flexGrow: 1,
gap: 2,
}}
>
<Box sx={{ display: "flex", flexDirection: "column", gap: 2 }}>
{selectedArticle && (
<Typography
variant="h4"
@ -1065,38 +1371,41 @@ export const SightEdit = observer(() => {
{selectedArticle.body}
</Typography>
)}
{selectedArticle && (
<Typography
variant="body1"
gutterBottom
sx={{ color: "text.primary" }}
>
{selectedArticle.body}
</Typography>
)}
{selectedArticle && (
<Typography
variant="body1"
gutterBottom
sx={{ color: "text.primary" }}
>
{selectedArticle.body}
</Typography>
)}
</Box>
<Box sx={{ display: "flex", gap: 1, mt: 2 }}>
)}
<Box>
<Typography variant="h6" gutterBottom>
Привязанные статьи
</Typography>
<Box
sx={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
borderRadius: 2,
background:
"linear-gradient(180deg, hsla(0,0%,100%,.2), hsla(0,0%,100%,0)), hsla(29,15%,65%,.4)",
boxShadow: "inset 4px 4px 12px hsla(0,0%,100%,.12)",
gap: 1,
mt: 2,
}}
>
{linkedArticles.map((article, index) => (
<Box
key={article.id}
onClick={() => setSelectedArticleIndex(index)}
onClick={() => {
setSelectedArticleIndex(index);
setPreviewSelected(false);
}}
sx={{
cursor: "pointer",
bgcolor:
selectedArticleIndex === index
? "primary.main"
: "transparent",
color: selectedArticleIndex === index ? "white" : "inherit",
color:
selectedArticleIndex === index
? "white"
: "inherit",
p: 1,
borderRadius: 1,
}}
@ -1108,6 +1417,9 @@ export const SightEdit = observer(() => {
))}
</Box>
</Box>
</Box>
}
</Box>
</Paper>
</CustomTabPanel>
@ -1134,7 +1446,6 @@ export const SightEdit = observer(() => {
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
autoComplete="off"
>
<LanguageSwitch />
<Controller
control={control}
name="thumbnail"
@ -1144,8 +1455,7 @@ export const SightEdit = observer(() => {
{...mediaAutocompleteProps}
value={
mediaAutocompleteProps.options.find(
(option) =>
option.id === field.value && option.media_type === 3
(option) => option.id === field.value
) || null
}
onChange={(_, value) => {
@ -1158,16 +1468,18 @@ export const SightEdit = observer(() => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
return options.filter(
(option) =>
option.media_name
.toLowerCase()
.includes(inputValue.toLowerCase())
.includes(inputValue.toLowerCase()) &&
option.media_type === 3
);
}}
renderInput={(params) => (
<TextField
{...params}
label="Выберите водный знак (Левый верх)"
label="Выберите логотип достопримечательности"
margin="normal"
variant="outlined"
error={!!errors.arms}
@ -1201,10 +1513,12 @@ export const SightEdit = observer(() => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
return options.filter(
(option) =>
option.media_name
.toLowerCase()
.includes(inputValue.toLowerCase())
.includes(inputValue.toLowerCase()) &&
option.media_type === 4
);
}}
renderInput={(params) => (
@ -1244,10 +1558,12 @@ export const SightEdit = observer(() => {
return option.id === value?.id;
}}
filterOptions={(options, { inputValue }) => {
return options.filter((option) =>
return options.filter(
(option) =>
option.media_name
.toLowerCase()
.includes(inputValue.toLowerCase())
.includes(inputValue.toLowerCase()) &&
option.media_type === 4
);
}}
renderInput={(params) => (
@ -1405,6 +1721,7 @@ export const SightEdit = observer(() => {
</Box>
</Edit>
</CustomTabPanel>
<ArticleEditModal />
</Box>
);
});