added more types for media

This commit is contained in:
2025-04-27 11:27:22 +03:00
parent abd054b8d4
commit 0d325a3aa6
206 changed files with 20273 additions and 18977 deletions

View File

@ -22,7 +22,7 @@ export const ArticleEdit = () => {
// useEffect(() => {
// Cookies.set("lang", initialLanguage);
// }, [pathname]);
const [language, setLanguage] = useState(Cookies.get("lang")!);
const [language, setLanguage] = useState(Cookies.get("lang") || "ru");
const [articleData, setArticleData] = useState<{
ru: { heading: string; body: string };
en: { heading: string; body: string };

View File

@ -20,7 +20,7 @@ export const CarrierList = observer(() => {
{
field: "cityID",
operator: "eq",
value: city_id,
value: city_id === "0" ? null : city_id,
},
],
},

View File

@ -1,4 +1,3 @@
import { Icons } from "../../preview/components";
import { Box, TextField } from "@mui/material";
import { Edit } from "@refinedev/mui";
import { useForm } from "@refinedev/react-hook-form";

View File

@ -0,0 +1,21 @@
import { Canvas } from "@react-three/fiber";
import { OrbitControls, Stage, useGLTF } from "@react-three/drei";
type ModelViewerProps = {
fileUrl: string;
};
export const ModelViewer = ({ fileUrl }: ModelViewerProps) => {
const { scene } = useGLTF(fileUrl);
return (
<Canvas style={{ width: "100%", height: "80vh" }}>
<ambientLight />
<directionalLight />
<Stage environment="city" intensity={0.6}>
<primitive object={scene} />
</Stage>
<OrbitControls />
</Canvas>
);
};

View File

@ -1,39 +1,58 @@
import {Box, TextField, Button, Typography, Autocomplete} from '@mui/material'
import {Create} from '@refinedev/mui'
import {useForm} from '@refinedev/react-hook-form'
import {Controller} from 'react-hook-form'
import {
Box,
TextField,
Button,
Typography,
Autocomplete,
} from "@mui/material";
import { Create } from "@refinedev/mui";
import { useForm } from "@refinedev/react-hook-form";
import { Controller } from "react-hook-form";
import {MEDIA_TYPES} from '../../lib/constants'
import {ALLOWED_IMAGE_TYPES, ALLOWED_VIDEO_TYPES, useMediaFileUpload} from '../../components/media/MediaFormUtils'
import { MEDIA_TYPES } from "../../lib/constants";
import {
ALLOWED_IMAGE_TYPES,
ALLOWED_ICON_TYPES,
ALLOWED_PANORAMA_TYPES,
ALLOWED_VIDEO_TYPES,
ALLOWED_WATERMARK_TYPES,
ALLOWED_3D_MODEL_TYPES,
useMediaFileUpload,
} from "../../components/media/MediaFormUtils";
import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer";
import { ModelViewer } from "./ModelViewer/index";
type MediaFormValues = {
media_name: string
media_type: number
file?: File
}
media_name: string;
media_type: number;
file?: File;
};
export const MediaCreate = () => {
const {
saveButtonProps,
refineCore: {formLoading, onFinish},
refineCore: { formLoading, onFinish },
register,
control,
formState: {errors},
formState: { errors },
setValue,
handleSubmit,
watch,
setError,
clearErrors,
} = useForm<MediaFormValues>({})
getValues,
} = useForm<MediaFormValues>({});
const selectedMediaType = watch('media_type')
const selectedMediaType = watch("media_type");
const file = getValues("file");
const {selectedFile, previewUrl, handleFileChange, handleMediaTypeChange} = useMediaFileUpload({
selectedMediaType,
setError,
clearErrors,
setValue,
})
const { selectedFile, previewUrl, handleFileChange, handleMediaTypeChange } =
useMediaFileUpload({
selectedMediaType,
setError,
clearErrors,
setValue,
});
return (
<Create
@ -42,19 +61,20 @@ export const MediaCreate = () => {
...saveButtonProps,
disabled: !!errors.file || !selectedFile,
onClick: handleSubmit((data) => {
console.log(data);
if (data.file) {
const formData = new FormData()
formData.append('media_name', data.media_name)
formData.append('filename', data.file.name)
formData.append('type', String(data.media_type))
formData.append('file', data.file)
const formData = new FormData();
formData.append("media_name", data.media_name);
formData.append("filename", data.file.name);
formData.append("type", String(data.media_type));
formData.append("file", data.file);
console.log('Отправляемые данные:')
console.log("Отправляемые данные:");
for (const pair of formData.entries()) {
console.log(pair[0] + ': ' + pair[1])
console.log(pair[0] + ": " + pair[1]);
}
onFinish(formData)
onFinish(formData);
}
}),
}}
@ -63,47 +83,97 @@ export const MediaCreate = () => {
control={control}
name="media_type"
rules={{
required: 'Это поле является обязательным',
required: "Это поле является обязательным",
}}
render={({field}) => (
render={({ field }) => (
<Autocomplete
options={MEDIA_TYPES}
value={MEDIA_TYPES.find((option) => option.value === field.value) || null}
value={
MEDIA_TYPES.find((option) => option.value === field.value) || null
}
onChange={(_, value) => {
field.onChange(value?.value || null)
handleMediaTypeChange(value?.value || null)
field.onChange(value?.value || null);
handleMediaTypeChange(value?.value || null);
}}
getOptionLabel={(item) => {
return item ? item.label : ''
return item ? item.label : "";
}}
isOptionEqualToValue={(option, value) => {
return option.value === value?.value
return option.value === value?.value;
}}
renderInput={(params) => <TextField {...params} label="Тип" margin="normal" variant="outlined" error={!!errors.media_type} helperText={(errors as any)?.media_type?.message} required />}
renderInput={(params) => (
<TextField
{...params}
label="Тип"
margin="normal"
variant="outlined"
error={!!errors.media_type}
helperText={(errors as any)?.media_type?.message}
required
/>
)}
/>
)}
/>
<TextField
{...register('media_name', {
required: 'Это поле является обязательным',
{...register("media_name", {
required: "Это поле является обязательным",
})}
error={!!(errors as any)?.media_name}
helperText={(errors as any)?.media_name?.message}
margin="normal"
fullWidth
InputLabelProps={{shrink: true}}
InputLabelProps={{ shrink: true }}
type="text"
label="Название *"
name="media_name"
/>
<Box component="form" sx={{display: 'flex', flexDirection: 'column'}} autoComplete="off" style={{marginTop: 10}}>
<Box display="flex" flexDirection="column-reverse" alignItems="center" gap={6}>
<Box display="flex" flexDirection="column" alignItems="center" gap={2}>
<Button variant="contained" component="label" disabled={!selectedMediaType}>
{selectedFile ? 'Изменить файл' : 'Загрузить файл'}
<input type="file" hidden onChange={handleFileChange} accept={selectedMediaType === 1 ? ALLOWED_IMAGE_TYPES.join(',') : ALLOWED_VIDEO_TYPES.join(',')} />
<Box
component="form"
sx={{ display: "flex", flexDirection: "column" }}
autoComplete="off"
style={{ marginTop: 10 }}
>
<Box
display="flex"
flexDirection="column-reverse"
alignItems="center"
gap={6}
>
<Box
display="flex"
flexDirection="column"
alignItems="center"
gap={2}
>
<Button
variant="contained"
component="label"
disabled={!selectedMediaType}
>
{selectedFile ? "Изменить файл" : "Загрузить файл"}
<input
type="file"
hidden
onChange={handleFileChange}
accept={
selectedMediaType === 6
? ALLOWED_3D_MODEL_TYPES.join(",")
: selectedMediaType === 1
? ALLOWED_IMAGE_TYPES.join(",")
: selectedMediaType === 2
? ALLOWED_VIDEO_TYPES.join(",")
: selectedMediaType === 3
? ALLOWED_ICON_TYPES.join(",")
: selectedMediaType === 4
? ALLOWED_WATERMARK_TYPES.join(",")
: selectedMediaType === 5
? ALLOWED_PANORAMA_TYPES.join(",")
: ""
}
/>
</Button>
{selectedFile && (
@ -121,11 +191,53 @@ export const MediaCreate = () => {
{previewUrl && selectedMediaType === 1 && (
<Box mt={2} display="flex" justifyContent="center">
<img src={previewUrl} alt="Preview" style={{maxWidth: '200px', borderRadius: 8}} />
<img
src={previewUrl}
alt="Preview"
style={{ maxWidth: "200px", borderRadius: 8 }}
/>
</Box>
)}
{file && selectedMediaType === 2 && (
<Box mt={2} display="flex" justifyContent="center">
<video src={URL.createObjectURL(file)} autoPlay controls />
</Box>
)}
{previewUrl && selectedMediaType === 3 && (
<Box mt={2} display="flex" justifyContent="center">
<img
src={previewUrl}
alt="Preview"
style={{ maxWidth: "200px", borderRadius: 8 }}
/>
</Box>
)}
{previewUrl && selectedMediaType === 4 && (
<Box mt={2} display="flex" justifyContent="center">
<img
src={previewUrl}
alt="Preview"
style={{ maxWidth: "200px", borderRadius: 8 }}
/>
</Box>
)}
{file && selectedMediaType === 5 && (
<ReactPhotoSphereViewer
src={URL.createObjectURL(file)}
width={"100%"}
height={"80vh"}
/>
)}
{file && previewUrl && selectedMediaType === 6 && (
<ModelViewer fileUrl={URL.createObjectURL(file)} />
)}
</Box>
</Box>
</Create>
)
}
);
};

View File

@ -11,13 +11,17 @@ import { useEffect } from "react";
import { useShow } from "@refinedev/core";
import { Controller } from "react-hook-form";
import { TOKEN_KEY } from "../../authProvider";
import { MEDIA_TYPES } from "../../lib/constants";
import {
ALLOWED_IMAGE_TYPES,
ALLOWED_VIDEO_TYPES,
ALLOWED_ICON_TYPES,
ALLOWED_WATERMARK_TYPES,
ALLOWED_PANORAMA_TYPES,
ALLOWED_3D_MODEL_TYPES,
useMediaFileUpload,
} from "../../components/media/MediaFormUtils";
import { TOKEN_KEY } from "../../authProvider";
type MediaFormValues = {
media_name: string;
@ -175,7 +179,17 @@ export const MediaEdit = () => {
accept={
selectedMediaType === 1
? ALLOWED_IMAGE_TYPES.join(",")
: ALLOWED_VIDEO_TYPES.join(",")
: selectedMediaType === 2
? ALLOWED_VIDEO_TYPES.join(",")
: selectedMediaType === 3
? ALLOWED_ICON_TYPES.join(",")
: selectedMediaType === 4
? ALLOWED_WATERMARK_TYPES.join(",")
: selectedMediaType === 5
? ALLOWED_PANORAMA_TYPES.join(",")
: selectedMediaType === 6
? ALLOWED_3D_MODEL_TYPES.join(",")
: ""
}
/>
</Button>

View File

@ -1,9 +1,11 @@
import { Stack, Typography, Box, Button } from "@mui/material";
import { useShow } from "@refinedev/core";
import { Show, TextFieldComponent as TextField } from "@refinedev/mui";
import { ReactPhotoSphereViewer } from "react-photo-sphere-viewer";
import sky from "./12414.jpg";
import { MEDIA_TYPES } from "../../lib/constants";
import { TOKEN_KEY } from "../../authProvider";
import { ModelViewer } from "./ModelViewer/index";
export const MediaShow = () => {
const { query } = useShow({});
@ -43,53 +45,67 @@ export const MediaShow = () => {
)}
{record && record.media_type === 2 && (
<>
<video
src={`${import.meta.env.VITE_KRBL_MEDIA}${
record?.id
}/download?token=${token}`}
style={{
maxWidth: "50%",
<video
src={`${import.meta.env.VITE_KRBL_MEDIA}${
record?.id
}/download?token=${token}`}
style={{
maxWidth: "50%",
objectFit: "contain",
borderRadius: 30,
}}
controls
autoPlay
muted
/>
<Box
sx={{
p: 2,
border: "1px solid text.pimary",
borderRadius: 2,
bgcolor: "primary.light",
width: "fit-content",
}}
>
<Typography
variant="body1"
gutterBottom
sx={{
color: "#FFFFFF",
}}
>
Видео доступно для скачивания по ссылке:
</Typography>
<Button
variant="contained"
href={`${import.meta.env.VITE_KRBL_MEDIA}${
record?.id
}/download?token=${token}`}
target="_blank"
sx={{ mt: 1, width: "100%" }}
>
Скачать видео
</Button>
</Box>
</>
objectFit: "contain",
borderRadius: 30,
}}
controls
autoPlay
muted
/>
)}
{record && record.media_type === 3 && (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
record?.id
}/download?token=${token}`}
alt={record?.filename}
style={{
maxWidth: "100%",
height: "40vh",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{record && record.media_type === 4 && (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
record?.id
}/download?token=${token}`}
alt={record?.filename}
style={{
maxWidth: "100%",
height: "40vh",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{record && record.media_type === 5 && (
<ReactPhotoSphereViewer
src={`${import.meta.env.VITE_KRBL_MEDIA}${
record?.id
}/download?token=${token}`}
width={"100%px"}
height={"80vh"}
/>
)}
{record && record.media_type === 6 && (
<ModelViewer
fileUrl={`${import.meta.env.VITE_KRBL_MEDIA}${
record?.id
}/download?token=${token}`}
/>
)}
{fields.map(({ label, data, render }) => (
<Stack key={data} gap={1}>
<Typography variant="body1" fontWeight="bold">
@ -100,6 +116,35 @@ export const MediaShow = () => {
/>
</Stack>
))}
<Box
sx={{
p: 2,
border: "1px solid text.pimary",
borderRadius: 2,
bgcolor: "primary.light",
width: "fit-content",
}}
>
<Typography
variant="body1"
gutterBottom
sx={{
color: "#FFFFFF",
}}
>
Доступно для скачивания по ссылке:
</Typography>
<Button
variant="contained"
href={`${import.meta.env.VITE_KRBL_MEDIA}${
record?.id
}/download?token=${token}`}
target="_blank"
sx={{ mt: 1, width: "100%" }}
>
Скачать медиа
</Button>
</Box>
</Stack>
</Show>
);

View File

@ -10,14 +10,15 @@ import {
import { Edit, useAutocomplete } from "@refinedev/mui";
import { useForm } from "@refinedev/react-hook-form";
import { Controller } from "react-hook-form";
import { useParams } from "react-router";
import { useParams, Link } from "react-router";
import React, { useState, useEffect } from "react";
import { LinkedItems } from "../../components/LinkedItems";
import { CreateSightArticle } from "../../components/CreateSightArticle";
import { ArticleItem, articleFields } from "./types";
import { TOKEN_KEY } from "../../authProvider";
import { Link } from "react-router";
import Cookies from "js-cookie";
import { observer } from "mobx-react-lite";
import { languageStore } from "../../store/LanguageStore";
function a11yProps(index: number) {
return {
@ -48,21 +49,9 @@ function CustomTabPanel(props: TabPanelProps) {
);
}
export const SightEdit = () => {
export const SightEdit = observer(() => {
const { id: sightId } = useParams<{ id: string }>();
const [language, setLanguage] = useState(Cookies.get("lang") || "ru");
const handleLanguageChange = (lang: string) => {
setLanguage(lang);
};
useEffect(() => {
const lang = Cookies.get("lang")!;
Cookies.set("lang", language);
return () => {
Cookies.set("lang", lang);
};
}, [language]);
const { language, setLanguageAction } = languageStore;
const {
saveButtonProps,
@ -91,9 +80,6 @@ export const SightEdit = () => {
value,
},
],
queryOptions: {
queryKey: ["sight", language],
},
});
const [tabValue, setTabValue] = useState(0);
const { autocompleteProps: mediaAutocompleteProps } = useAutocomplete({
@ -252,10 +238,12 @@ export const SightEdit = () => {
onChange={(_, newValue) => setTabValue(newValue)}
aria-label="basic tabs example"
>
<Tab label="Основная информация" {...a11yProps(0)} />
<Tab label="Статьи" {...a11yProps(1)} />
<Tab label="Основная информация" {...a11yProps(1)} />
<Tab label="Левый виджет" {...a11yProps(2)} />
<Tab label="Правый информация" {...a11yProps(3)} />
</Tabs>
</Box>
<CustomTabPanel value={tabValue} index={0}>
<Edit saveButtonProps={saveButtonProps}>
<Box sx={{ display: "flex", gap: 2 }}>
@ -287,7 +275,7 @@ export const SightEdit = () => {
p: 1,
borderRadius: 1,
}}
onClick={() => handleLanguageChange("ru")}
onClick={() => setLanguageAction("ru")}
>
RU
</Box>
@ -302,7 +290,7 @@ export const SightEdit = () => {
p: 1,
borderRadius: 1,
}}
onClick={() => handleLanguageChange("en")}
onClick={() => setLanguageAction("en")}
>
EN
</Box>
@ -317,7 +305,7 @@ export const SightEdit = () => {
p: 1,
borderRadius: 1,
}}
onClick={() => handleLanguageChange("zh")}
onClick={() => setLanguageAction("zh")}
>
ZH
</Box>
@ -896,14 +884,10 @@ export const SightEdit = () => {
</Box>
</Paper>
</Box>
</Edit>
</CustomTabPanel>
<CustomTabPanel value={tabValue} index={1}>
{sightId && (
<Box sx={{ mt: 3 }}>
<LinkedItems<ArticleItem>
type="edit"
parentId={sightId}
parentId={sightId!}
parentResource="sight"
childResource="article"
fields={articleFields}
@ -911,14 +895,20 @@ export const SightEdit = () => {
/>
<CreateSightArticle
parentId={sightId}
parentId={sightId!}
parentResource="sight"
childResource="article"
title="статью"
/>
</Box>
)}
</Edit>
</CustomTabPanel>
<CustomTabPanel value={tabValue} index={1}>
1
</CustomTabPanel>
<CustomTabPanel value={tabValue} index={2}>
2
</CustomTabPanel>
</Box>
);
};
});

View File

@ -22,7 +22,7 @@ export const SightList = observer(() => {
{
field: "cityID",
operator: "eq",
value: city_id,
value: city_id === "0" ? null : city_id,
},
],
},

View File

@ -23,7 +23,7 @@ export const StationList = observer(() => {
{
field: "cityID",
operator: "eq",
value: city_id,
value: city_id === "0" ? null : city_id,
},
],
},