rewrite code with edit article in sight page
This commit is contained in:
parent
dc483d62de
commit
65532f7074
@ -33,6 +33,7 @@ type Props = {
|
|||||||
parentResource: string;
|
parentResource: string;
|
||||||
childResource: string;
|
childResource: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
left?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CreateSightArticle = ({
|
export const CreateSightArticle = ({
|
||||||
@ -40,6 +41,7 @@ export const CreateSightArticle = ({
|
|||||||
parentResource,
|
parentResource,
|
||||||
childResource,
|
childResource,
|
||||||
title,
|
title,
|
||||||
|
left,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
|
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
|
||||||
@ -118,7 +120,8 @@ export const CreateSightArticle = ({
|
|||||||
const existingItems = existingItemsResponse.data || [];
|
const existingItems = existingItemsResponse.data || [];
|
||||||
const nextPageNum = existingItems.length + 1;
|
const nextPageNum = existingItems.length + 1;
|
||||||
|
|
||||||
// Привязываем статью к достопримечательности
|
if (!left) {
|
||||||
|
// Привязываем статью к достопримечательности если она не левая
|
||||||
await axiosInstance.post(
|
await axiosInstance.post(
|
||||||
`${
|
`${
|
||||||
import.meta.env.VITE_KRBL_API
|
import.meta.env.VITE_KRBL_API
|
||||||
@ -128,6 +131,7 @@ export const CreateSightArticle = ({
|
|||||||
page_num: nextPageNum,
|
page_num: nextPageNum,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Загружаем все медиа файлы и получаем их ID
|
// Загружаем все медиа файлы и получаем их ID
|
||||||
const mediaIds = await Promise.all(
|
const mediaIds = await Promise.all(
|
||||||
@ -174,6 +178,7 @@ export const CreateSightArticle = ({
|
|||||||
marginTop: 2,
|
marginTop: 2,
|
||||||
background: theme.palette.background.paper,
|
background: theme.palette.background.paper,
|
||||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||||
|
zIndex: 2000,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="subtitle1" fontWeight="bold">
|
<Typography variant="subtitle1" fontWeight="bold">
|
||||||
@ -192,6 +197,10 @@ export const CreateSightArticle = ({
|
|||||||
fullWidth
|
fullWidth
|
||||||
InputLabelProps={{ shrink: true }}
|
InputLabelProps={{ shrink: true }}
|
||||||
type="text"
|
type="text"
|
||||||
|
sx={{
|
||||||
|
zIndex: 2000,
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
}}
|
||||||
label="Заголовок *"
|
label="Заголовок *"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Close } from "@mui/icons-material";
|
import { languageStore } from "../store/LanguageStore";
|
||||||
import {
|
import {
|
||||||
Stack,
|
Stack,
|
||||||
Typography,
|
Typography,
|
||||||
@ -88,6 +88,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
type,
|
type,
|
||||||
onSave,
|
onSave,
|
||||||
}: LinkedItemsProps<T>) => {
|
}: LinkedItemsProps<T>) => {
|
||||||
|
const { language } = languageStore;
|
||||||
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
|
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
|
||||||
const { setStationModalOpenAction, setStationIdAction } = stationStore;
|
const { setStationModalOpenAction, setStationIdAction } = stationStore;
|
||||||
const [position, setPosition] = useState<number>(1);
|
const [position, setPosition] = useState<number>(1);
|
||||||
@ -152,7 +153,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
setLinkedItems([]);
|
setLinkedItems([]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [parentId, parentResource, childResource]);
|
}, [parentId, parentResource, childResource, language]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (type === "edit") {
|
if (type === "edit") {
|
||||||
@ -354,7 +355,10 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
color="error"
|
color="error"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => deleteItem(item.id)}
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
deleteItem(item.id);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Отвязать
|
Отвязать
|
||||||
</Button>
|
</Button>
|
||||||
@ -430,7 +434,7 @@ export const LinkedItems = <T extends { id: number; [key: string]: any }>({
|
|||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
label="Номер страницы"
|
label="Позиция добавляемой статьи"
|
||||||
name="page_num"
|
name="page_num"
|
||||||
value={pageNum}
|
value={pageNum}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
|
@ -4,16 +4,30 @@ import { observer } from "mobx-react-lite";
|
|||||||
import { useForm } from "@refinedev/react-hook-form";
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
import { Controller } from "react-hook-form";
|
import { Controller } from "react-hook-form";
|
||||||
import "easymde/dist/easymde.min.css";
|
import "easymde/dist/easymde.min.css";
|
||||||
import { memo, useMemo, useEffect } from "react";
|
import { memo, useMemo, useEffect, useCallback } from "react";
|
||||||
import { MarkdownEditor } from "../../MarkdownEditor";
|
import { MarkdownEditor } from "../../MarkdownEditor";
|
||||||
import { Edit } from "@refinedev/mui";
|
import { Edit } from "@refinedev/mui";
|
||||||
import { languageStore } from "../../../store/LanguageStore";
|
import { languageStore } from "../../../store/LanguageStore";
|
||||||
import { LanguageSwitch } from "../../LanguageSwitch/index";
|
import { LanguageSwitch } from "../../LanguageSwitch/index";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { useState } from "react";
|
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);
|
const MemoizedSimpleMDE = memo(MarkdownEditor);
|
||||||
|
|
||||||
|
type MediaFile = {
|
||||||
|
file: File;
|
||||||
|
preview: string;
|
||||||
|
uploading: boolean;
|
||||||
|
mediaId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
const style = {
|
const style = {
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
top: "50%",
|
top: "50%",
|
||||||
@ -45,12 +59,58 @@ export const ArticleEditModal = observer(() => {
|
|||||||
articleStore;
|
articleStore;
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
|
const [mediaFiles, setMediaFiles] = useState<MediaFile[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
setArticleModalOpenAction(false);
|
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 {
|
const {
|
||||||
register,
|
register,
|
||||||
control,
|
control,
|
||||||
@ -66,10 +126,37 @@ export const ArticleEditModal = observer(() => {
|
|||||||
action: "edit",
|
action: "edit",
|
||||||
redirect: false,
|
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);
|
setArticleModalOpenAction(false);
|
||||||
reset();
|
reset();
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error handling media:", error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
meta: {
|
meta: {
|
||||||
headers: {
|
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 (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={articleModalOpen}
|
open={articleModalOpen}
|
||||||
@ -164,6 +310,88 @@ export const ArticleEditModal = observer(() => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</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>
|
</Edit>
|
||||||
</Box>
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
@ -6,11 +6,11 @@ type ModelViewerProps = {
|
|||||||
height?: string;
|
height?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModelViewer = ({ fileUrl, height }: ModelViewerProps) => {
|
export const ModelViewer = ({ fileUrl, height = "80vh" }: ModelViewerProps) => {
|
||||||
const { scene } = useGLTF(fileUrl);
|
const { scene } = useGLTF(fileUrl);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Canvas style={{ width: "100%", height: "400px" }}>
|
<Canvas style={{ width: "100%", height: height }}>
|
||||||
<ambientLight />
|
<ambientLight />
|
||||||
<directionalLight />
|
<directionalLight />
|
||||||
<Stage environment="city" intensity={0.6}>
|
<Stage environment="city" intensity={0.6}>
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
|
Button,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Edit, useAutocomplete } from "@refinedev/mui";
|
import { Edit, useAutocomplete } from "@refinedev/mui";
|
||||||
import { useForm } from "@refinedev/react-hook-form";
|
import { useForm } from "@refinedev/react-hook-form";
|
||||||
@ -23,6 +24,8 @@ import axios from "axios";
|
|||||||
import { LanguageSwitch } from "../../components/LanguageSwitch/index";
|
import { LanguageSwitch } from "../../components/LanguageSwitch/index";
|
||||||
import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer";
|
import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer";
|
||||||
import { ModelViewer } from "../media/ModelViewer";
|
import { ModelViewer } from "../media/ModelViewer";
|
||||||
|
import { articleStore } from "../../store/ArticleStore";
|
||||||
|
import { ArticleEditModal } from "../../components/modals/ArticleEditModal/index";
|
||||||
|
|
||||||
function a11yProps(index: number) {
|
function a11yProps(index: number) {
|
||||||
return {
|
return {
|
||||||
@ -56,7 +59,8 @@ function CustomTabPanel(props: TabPanelProps) {
|
|||||||
export const SightEdit = observer(() => {
|
export const SightEdit = observer(() => {
|
||||||
const { id: sightId } = useParams<{ id: string }>();
|
const { id: sightId } = useParams<{ id: string }>();
|
||||||
const { language, setLanguageAction } = languageStore;
|
const { language, setLanguageAction } = languageStore;
|
||||||
|
const [previewSelected, setPreviewSelected] = useState(true);
|
||||||
|
const { setArticleModalOpenAction, setArticleIdAction } = articleStore;
|
||||||
const [sightData, setSightData] = useState({
|
const [sightData, setSightData] = useState({
|
||||||
ru: {
|
ru: {
|
||||||
name: "",
|
name: "",
|
||||||
@ -129,13 +133,16 @@ export const SightEdit = observer(() => {
|
|||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { autocompleteProps: articleAutocompleteProps } = useAutocomplete({
|
const { autocompleteProps: articleAutocompleteProps } = useAutocomplete({
|
||||||
resource: "article",
|
resource: "article",
|
||||||
queryOptions: {
|
|
||||||
queryKey: ["article", language],
|
|
||||||
},
|
|
||||||
onSearch: (value) => [
|
onSearch: (value) => [
|
||||||
{
|
{
|
||||||
field: "heading",
|
field: "heading",
|
||||||
@ -148,6 +155,12 @@ export const SightEdit = observer(() => {
|
|||||||
value,
|
value,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
|
meta: {
|
||||||
|
headers: {
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -189,7 +202,7 @@ export const SightEdit = observer(() => {
|
|||||||
latitude: "",
|
latitude: "",
|
||||||
longitude: "",
|
longitude: "",
|
||||||
});
|
});
|
||||||
const [selectedArticleIndex, setSelectedArticleIndex] = useState(0);
|
const [selectedArticleIndex, setSelectedArticleIndex] = useState(-1);
|
||||||
const [cityPreview, setCityPreview] = useState("");
|
const [cityPreview, setCityPreview] = useState("");
|
||||||
const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null);
|
const [thumbnailPreview, setThumbnailPreview] = useState<string | null>(null);
|
||||||
const [watermarkLUPreview, setWatermarkLUPreview] = useState<string | null>(
|
const [watermarkLUPreview, setWatermarkLUPreview] = useState<string | null>(
|
||||||
@ -198,11 +211,64 @@ export const SightEdit = observer(() => {
|
|||||||
const [watermarkRDPreview, setWatermarkRDPreview] = useState<string | null>(
|
const [watermarkRDPreview, setWatermarkRDPreview] = useState<string | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [leftArticlePreview, setLeftArticlePreview] = useState("");
|
|
||||||
const [previewArticlePreview, setPreviewArticlePreview] = useState("");
|
|
||||||
const [linkedArticles, setLinkedArticles] = useState<ArticleItem[]>([]);
|
const [linkedArticles, setLinkedArticles] = useState<ArticleItem[]>([]);
|
||||||
// Следим за изменениями во всех полях
|
// Следим за изменениями во всех полях
|
||||||
const selectedArticle = linkedArticles[selectedArticleIndex];
|
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 addressContent = watch("address");
|
||||||
const nameContent = watch("name");
|
const nameContent = watch("name");
|
||||||
@ -244,6 +310,7 @@ export const SightEdit = observer(() => {
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY)}`,
|
Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY)}`,
|
||||||
|
"Accept-Language": language,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -322,18 +389,36 @@ export const SightEdit = observer(() => {
|
|||||||
}, [watermarkRDContent, mediaAutocompleteProps.options]);
|
}, [watermarkRDContent, mediaAutocompleteProps.options]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
const getMedia = async () => {
|
||||||
const selectedLeftArticle = articleAutocompleteProps.options.find(
|
const selectedLeftArticle = articleAutocompleteProps.options.find(
|
||||||
(option) => option.id === leftArticleContent
|
(option) => option.id === leftArticleContent
|
||||||
);
|
);
|
||||||
setLeftArticlePreview(selectedLeftArticle?.heading || "");
|
if (!selectedLeftArticle) return;
|
||||||
}, [leftArticleContent, articleAutocompleteProps.options]);
|
const response = await axios.get(
|
||||||
|
`${import.meta.env.VITE_KRBL_API}/article/${
|
||||||
useEffect(() => {
|
selectedLeftArticle?.id
|
||||||
const selectedPreviewArticle = articleAutocompleteProps.options.find(
|
}/media`,
|
||||||
(option) => option.id === previewArticleContent
|
{
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem(TOKEN_KEY)}`,
|
||||||
|
"Accept-Language": language,
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
setPreviewArticlePreview(selectedPreviewArticle?.heading || "");
|
const media = response.data[0];
|
||||||
}, [previewArticleContent, articleAutocompleteProps.options]);
|
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) => {
|
const handleLanguageChange = (lang: string) => {
|
||||||
setSightData((prevData) => ({
|
setSightData((prevData) => ({
|
||||||
@ -567,6 +652,7 @@ export const SightEdit = observer(() => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ display: "none" }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="thumbnail"
|
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" }}>
|
<Box sx={{ display: "none" }}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
@ -715,6 +846,7 @@ export const SightEdit = observer(() => {
|
|||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
field.onChange(value?.id || "");
|
field.onChange(value?.id || "");
|
||||||
|
setLeftArticleMedia(null);
|
||||||
}}
|
}}
|
||||||
getOptionLabel={(item) => {
|
getOptionLabel={(item) => {
|
||||||
return item ? item.heading : "";
|
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>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@ -797,20 +886,99 @@ export const SightEdit = observer(() => {
|
|||||||
width: "30%",
|
width: "30%",
|
||||||
|
|
||||||
top: "179px",
|
top: "179px",
|
||||||
|
minHeight: "600px",
|
||||||
right: 50,
|
right: 50,
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
border: "1px solid",
|
border: "1px solid",
|
||||||
borderColor: "primary.main",
|
borderColor: "primary.main",
|
||||||
bgcolor: (theme) =>
|
bgcolor: "#806c59",
|
||||||
theme.palette.mode === "dark" ? "background.paper" : "#fff",
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography variant="h6" gutterBottom color="primary">
|
<Box
|
||||||
Предпросмотр
|
sx={{
|
||||||
</Typography>
|
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
|
<Typography
|
||||||
variant="h4"
|
variant="h4"
|
||||||
@ -855,34 +1023,34 @@ export const SightEdit = observer(() => {
|
|||||||
{addressContent}
|
{addressContent}
|
||||||
</Box>
|
</Box>
|
||||||
</Typography>
|
</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>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
</Edit>
|
</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>
|
||||||
|
|
||||||
<CustomTabPanel value={tabValue} index={1}>
|
<CustomTabPanel value={tabValue} index={1}>
|
||||||
@ -909,18 +1077,52 @@ export const SightEdit = observer(() => {
|
|||||||
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||||
autoComplete="off"
|
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
|
<TextField
|
||||||
{...register("name", {
|
{...params}
|
||||||
required: "Это поле является обязательным",
|
onClick={() => {
|
||||||
})}
|
setPreviewSelected(true);
|
||||||
error={!!(errors as any)?.name}
|
setSelectedArticleIndex(-1);
|
||||||
helperText={(errors as any)?.name?.message}
|
}}
|
||||||
|
label="Медиа-предпросмотр"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
fullWidth
|
variant="outlined"
|
||||||
InputLabelProps={{ shrink: true }}
|
error={!!errors.arms}
|
||||||
type="text"
|
helperText={(errors as any)?.arms?.message}
|
||||||
label={"Название *"}
|
required
|
||||||
name="name"
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<LanguageSwitch />
|
<LanguageSwitch />
|
||||||
@ -941,6 +1143,7 @@ export const SightEdit = observer(() => {
|
|||||||
parentResource="sight"
|
parentResource="sight"
|
||||||
childResource="article"
|
childResource="article"
|
||||||
title="статью"
|
title="статью"
|
||||||
|
left
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -951,33 +1154,45 @@ export const SightEdit = observer(() => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
position: "fixed",
|
position: "fixed",
|
||||||
p: 2,
|
p: 2,
|
||||||
|
height: "max-content",
|
||||||
width: "30%",
|
width: "30%",
|
||||||
overflowY: "scroll",
|
|
||||||
height: "80vh",
|
|
||||||
top: "178px",
|
|
||||||
|
|
||||||
|
top: "178px",
|
||||||
|
minHeight: "600px",
|
||||||
right: 50,
|
right: 50,
|
||||||
zIndex: 1000,
|
zIndex: 1000,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
border: "1px solid",
|
border: "1px solid",
|
||||||
borderColor: "primary.main",
|
|
||||||
bgcolor: (theme) =>
|
bgcolor: "#806c59",
|
||||||
theme.palette.mode === "dark" ? "background.paper" : "#fff",
|
}}
|
||||||
|
>
|
||||||
|
<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 && (
|
{mediaFile && mediaFile.src && mediaFile.media_type === 1 && (
|
||||||
<img
|
<img
|
||||||
src={mediaFile.src}
|
src={mediaFile.src}
|
||||||
alt={mediaFile.filename}
|
alt={mediaFile.filename}
|
||||||
style={{
|
style={{
|
||||||
maxWidth: "100%",
|
maxWidth: "100%",
|
||||||
height: "40vh",
|
height: "300px",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
}}
|
}}
|
||||||
@ -1026,26 +1241,117 @@ export const SightEdit = observer(() => {
|
|||||||
{mediaFile && mediaFile.src && mediaFile.media_type == 5 && (
|
{mediaFile && mediaFile.src && mediaFile.media_type == 5 && (
|
||||||
<ReactPhotoSphereViewer
|
<ReactPhotoSphereViewer
|
||||||
src={mediaFile.src}
|
src={mediaFile.src}
|
||||||
width={"100%"}
|
|
||||||
height={"300px"}
|
height={"300px"}
|
||||||
|
width={"350px"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{mediaFile && mediaFile.media_type === 6 && (
|
{mediaFile && mediaFile.media_type === 6 && (
|
||||||
<ModelViewer height={"30%"} fileUrl={mediaFile.src} />
|
<ModelViewer height={"400px"} fileUrl={mediaFile.src} />
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{/* Водяные знаки */}
|
)}
|
||||||
|
|
||||||
|
{
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
mt: 2,
|
||||||
mb: 2,
|
mb: 2,
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
justifyContent: "space-between",
|
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 && (
|
{selectedArticle && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="h4"
|
variant="h4"
|
||||||
@ -1065,38 +1371,41 @@ export const SightEdit = observer(() => {
|
|||||||
{selectedArticle.body}
|
{selectedArticle.body}
|
||||||
</Typography>
|
</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>
|
||||||
|
)}
|
||||||
<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) => (
|
{linkedArticles.map((article, index) => (
|
||||||
<Box
|
<Box
|
||||||
key={article.id}
|
key={article.id}
|
||||||
onClick={() => setSelectedArticleIndex(index)}
|
onClick={() => {
|
||||||
|
setSelectedArticleIndex(index);
|
||||||
|
setPreviewSelected(false);
|
||||||
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
bgcolor:
|
bgcolor:
|
||||||
selectedArticleIndex === index
|
selectedArticleIndex === index
|
||||||
? "primary.main"
|
? "primary.main"
|
||||||
: "transparent",
|
: "transparent",
|
||||||
color: selectedArticleIndex === index ? "white" : "inherit",
|
color:
|
||||||
|
selectedArticleIndex === index
|
||||||
|
? "white"
|
||||||
|
: "inherit",
|
||||||
p: 1,
|
p: 1,
|
||||||
borderRadius: 1,
|
borderRadius: 1,
|
||||||
}}
|
}}
|
||||||
@ -1108,6 +1417,9 @@ export const SightEdit = observer(() => {
|
|||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</CustomTabPanel>
|
</CustomTabPanel>
|
||||||
|
|
||||||
@ -1134,7 +1446,6 @@ export const SightEdit = observer(() => {
|
|||||||
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
sx={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<LanguageSwitch />
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="thumbnail"
|
name="thumbnail"
|
||||||
@ -1144,8 +1455,7 @@ export const SightEdit = observer(() => {
|
|||||||
{...mediaAutocompleteProps}
|
{...mediaAutocompleteProps}
|
||||||
value={
|
value={
|
||||||
mediaAutocompleteProps.options.find(
|
mediaAutocompleteProps.options.find(
|
||||||
(option) =>
|
(option) => option.id === field.value
|
||||||
option.id === field.value && option.media_type === 3
|
|
||||||
) || null
|
) || null
|
||||||
}
|
}
|
||||||
onChange={(_, value) => {
|
onChange={(_, value) => {
|
||||||
@ -1158,16 +1468,18 @@ export const SightEdit = observer(() => {
|
|||||||
return option.id === value?.id;
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter((option) =>
|
return options.filter(
|
||||||
|
(option) =>
|
||||||
option.media_name
|
option.media_name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase())
|
.includes(inputValue.toLowerCase()) &&
|
||||||
|
option.media_type === 3
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label="Выберите водный знак (Левый верх)"
|
label="Выберите логотип достопримечательности"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
error={!!errors.arms}
|
error={!!errors.arms}
|
||||||
@ -1201,10 +1513,12 @@ export const SightEdit = observer(() => {
|
|||||||
return option.id === value?.id;
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter((option) =>
|
return options.filter(
|
||||||
|
(option) =>
|
||||||
option.media_name
|
option.media_name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase())
|
.includes(inputValue.toLowerCase()) &&
|
||||||
|
option.media_type === 4
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
@ -1244,10 +1558,12 @@ export const SightEdit = observer(() => {
|
|||||||
return option.id === value?.id;
|
return option.id === value?.id;
|
||||||
}}
|
}}
|
||||||
filterOptions={(options, { inputValue }) => {
|
filterOptions={(options, { inputValue }) => {
|
||||||
return options.filter((option) =>
|
return options.filter(
|
||||||
|
(option) =>
|
||||||
option.media_name
|
option.media_name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.includes(inputValue.toLowerCase())
|
.includes(inputValue.toLowerCase()) &&
|
||||||
|
option.media_type === 4
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
@ -1405,6 +1721,7 @@ export const SightEdit = observer(() => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Edit>
|
</Edit>
|
||||||
</CustomTabPanel>
|
</CustomTabPanel>
|
||||||
|
<ArticleEditModal />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user