Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2afdb1e143 | |||
| 1235272161 | |||
| 3526648d1f |
@@ -13,7 +13,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { MediaViewer } from "@widgets";
|
import { MediaViewer, VideoPreviewCard } from "@widgets";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Loader2, Save, Plus } from "lucide-react";
|
import { ArrowLeft, Loader2, Save, Plus } from "lucide-react";
|
||||||
import { useEffect, useState, useMemo } from "react";
|
import { useEffect, useState, useMemo } from "react";
|
||||||
@@ -24,9 +24,10 @@ import { articlesStore } from "../../../shared/store/ArticlesStore";
|
|||||||
import { Route, routeStore } from "../../../shared/store/RouteStore";
|
import { Route, routeStore } from "../../../shared/store/RouteStore";
|
||||||
import {
|
import {
|
||||||
languageStore,
|
languageStore,
|
||||||
SelectArticleModal,
|
ArticleSelectOrCreateDialog,
|
||||||
SelectMediaDialog,
|
SelectMediaDialog,
|
||||||
selectedCityStore,
|
selectedCityStore,
|
||||||
|
UploadMediaDialog,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
|
|
||||||
export const RouteCreatePage = observer(() => {
|
export const RouteCreatePage = observer(() => {
|
||||||
@@ -39,6 +40,7 @@ export const RouteCreatePage = observer(() => {
|
|||||||
const [direction, setDirection] = useState("backward");
|
const [direction, setDirection] = useState("backward");
|
||||||
const [scaleMin, setScaleMin] = useState("");
|
const [scaleMin, setScaleMin] = useState("");
|
||||||
const [scaleMax, setScaleMax] = useState("");
|
const [scaleMax, setScaleMax] = useState("");
|
||||||
|
const [routeName, setRouteName] = useState("");
|
||||||
const [turn, setTurn] = useState("");
|
const [turn, setTurn] = useState("");
|
||||||
const [centerLat, setCenterLat] = useState("");
|
const [centerLat, setCenterLat] = useState("");
|
||||||
const [centerLng, setCenterLng] = useState("");
|
const [centerLng, setCenterLng] = useState("");
|
||||||
@@ -48,6 +50,8 @@ export const RouteCreatePage = observer(() => {
|
|||||||
useState(false);
|
useState(false);
|
||||||
const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false);
|
const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false);
|
||||||
const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false);
|
const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false);
|
||||||
|
const [isUploadVideoDialogOpen, setIsUploadVideoDialogOpen] = useState(false);
|
||||||
|
const [fileToUpload, setFileToUpload] = useState<File | null>(null);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -55,7 +59,6 @@ export const RouteCreatePage = observer(() => {
|
|||||||
articlesStore.getArticleList();
|
articlesStore.getArticleList();
|
||||||
}, [language]);
|
}, [language]);
|
||||||
|
|
||||||
// Фильтруем перевозчиков только из выбранного города
|
|
||||||
const filteredCarriers = useMemo(() => {
|
const filteredCarriers = useMemo(() => {
|
||||||
const carriers =
|
const carriers =
|
||||||
carrierStore.carriers[language as keyof typeof carrierStore.carriers]
|
carrierStore.carriers[language as keyof typeof carrierStore.carriers]
|
||||||
@@ -110,6 +113,8 @@ export const RouteCreatePage = observer(() => {
|
|||||||
const handleArticleSelect = (articleId: number) => {
|
const handleArticleSelect = (articleId: number) => {
|
||||||
setGovernorAppeal(articleId.toString());
|
setGovernorAppeal(articleId.toString());
|
||||||
setIsSelectArticleDialogOpen(false);
|
setIsSelectArticleDialogOpen(false);
|
||||||
|
// Обновляем список статей после создания новой
|
||||||
|
articlesStore.getArticleList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVideoSelect = (media: {
|
const handleVideoSelect = (media: {
|
||||||
@@ -122,6 +127,26 @@ export const RouteCreatePage = observer(() => {
|
|||||||
setIsSelectVideoDialogOpen(false);
|
setIsSelectVideoDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleVideoFileSelect = (file?: File) => {
|
||||||
|
if (file) {
|
||||||
|
setFileToUpload(file);
|
||||||
|
setIsUploadVideoDialogOpen(true);
|
||||||
|
} else {
|
||||||
|
setIsSelectVideoDialogOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVideoUpload = (media: {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
media_name?: string;
|
||||||
|
media_type: number;
|
||||||
|
}) => {
|
||||||
|
setVideoPreview(media.id);
|
||||||
|
setIsUploadVideoDialogOpen(false);
|
||||||
|
setFileToUpload(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleVideoPreviewClick = () => {
|
const handleVideoPreviewClick = () => {
|
||||||
setIsVideoPreviewOpen(true);
|
setIsVideoPreviewOpen(true);
|
||||||
};
|
};
|
||||||
@@ -167,6 +192,7 @@ export const RouteCreatePage = observer(() => {
|
|||||||
route_number: routeNumber,
|
route_number: routeNumber,
|
||||||
route_sys_number: govRouteNumber,
|
route_sys_number: govRouteNumber,
|
||||||
governor_appeal,
|
governor_appeal,
|
||||||
|
route_name: routeName,
|
||||||
route_direction,
|
route_direction,
|
||||||
scale_min,
|
scale_min,
|
||||||
scale_max,
|
scale_max,
|
||||||
@@ -208,6 +234,13 @@ export const RouteCreatePage = observer(() => {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-10 w-full items-end">
|
<div className="flex flex-col gap-10 w-full items-end">
|
||||||
<Box className="flex flex-col gap-6 w-full">
|
<Box className="flex flex-col gap-6 w-full">
|
||||||
|
<TextField
|
||||||
|
className="w-full"
|
||||||
|
label="Название маршрута"
|
||||||
|
required
|
||||||
|
value={routeName}
|
||||||
|
onChange={(e) => setRouteName(e.target.value)}
|
||||||
|
/>
|
||||||
<FormControl fullWidth required>
|
<FormControl fullWidth required>
|
||||||
<InputLabel>Выберите перевозчика</InputLabel>
|
<InputLabel>Выберите перевозчика</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@@ -247,7 +280,6 @@ export const RouteCreatePage = observer(() => {
|
|||||||
const lines = routeCoords.split("\n");
|
const lines = routeCoords.split("\n");
|
||||||
const lastLine = lines[lines.length - 1];
|
const lastLine = lines[lines.length - 1];
|
||||||
|
|
||||||
// Если мы на последней строке и она не пустая
|
|
||||||
if (lastLine && lastLine.trim()) {
|
if (lastLine && lastLine.trim()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newValue = routeCoords + "\n";
|
const newValue = routeCoords + "\n";
|
||||||
@@ -279,6 +311,7 @@ export const RouteCreatePage = observer(() => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
className="w-full"
|
className="w-full"
|
||||||
label="Номер маршрута в Говорящем Городе"
|
label="Номер маршрута в Говорящем Городе"
|
||||||
@@ -287,99 +320,42 @@ export const RouteCreatePage = observer(() => {
|
|||||||
onChange={(e) => setGovRouteNumber(e.target.value)}
|
onChange={(e) => setGovRouteNumber(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Заменяем Select на кнопку для выбора статьи */}
|
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
|
||||||
<Box className="flex flex-col gap-2">
|
Обращение к пассажирам
|
||||||
<label className="text-sm font-medium text-gray-700">
|
</Typography>
|
||||||
Обращение к пассажирам
|
<Box className="flex gap-2">
|
||||||
</label>
|
<TextField
|
||||||
<Box className="flex gap-2">
|
className="flex-1"
|
||||||
<TextField
|
value={selectedArticle?.heading || "Статья не выбрана"}
|
||||||
className="flex-1"
|
placeholder="Выберите статью"
|
||||||
value={selectedArticle?.heading || "Статья не выбрана"}
|
disabled
|
||||||
placeholder="Выберите статью"
|
fullWidth
|
||||||
disabled
|
sx={{
|
||||||
sx={{
|
"& .MuiInputBase-input": {
|
||||||
"& .MuiInputBase-input": {
|
color: selectedArticle ? "inherit" : "#999",
|
||||||
color: selectedArticle ? "inherit" : "#999",
|
},
|
||||||
},
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
<Button
|
||||||
<Button
|
variant="outlined"
|
||||||
variant="outlined"
|
onClick={() => setIsSelectArticleDialogOpen(true)}
|
||||||
onClick={() => setIsSelectArticleDialogOpen(true)}
|
startIcon={<Plus size={16} />}
|
||||||
startIcon={<Plus size={16} />}
|
sx={{ minWidth: "auto", px: 2 }}
|
||||||
sx={{ minWidth: "auto", px: 2 }}
|
>
|
||||||
>
|
Выбрать
|
||||||
Выбрать
|
</Button>
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Селектор видеозаставки */}
|
<VideoPreviewCard
|
||||||
<Box className="flex flex-col gap-2">
|
title="Видеозаставка"
|
||||||
<label className="text-sm font-medium text-gray-700">
|
videoId={videoPreview}
|
||||||
Видеозаставка
|
onVideoClick={handleVideoPreviewClick}
|
||||||
</label>
|
onDeleteVideoClick={() => {
|
||||||
<Box className="flex gap-2">
|
setVideoPreview("");
|
||||||
<Box
|
}}
|
||||||
className="flex-1"
|
onSelectVideoClick={handleVideoFileSelect}
|
||||||
onClick={handleVideoPreviewClick}
|
className="w-full"
|
||||||
sx={{
|
/>
|
||||||
cursor:
|
|
||||||
videoPreview && videoPreview !== "" ? "pointer" : "default",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
className="w-full h-[50px] border border-gray-400 rounded-sm flex items-center justify-between px-4"
|
|
||||||
sx={{
|
|
||||||
"& .MuiInputBase-input": {
|
|
||||||
color:
|
|
||||||
videoPreview && videoPreview !== ""
|
|
||||||
? "inherit"
|
|
||||||
: "#999",
|
|
||||||
cursor:
|
|
||||||
videoPreview && videoPreview !== ""
|
|
||||||
? "pointer"
|
|
||||||
: "default",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body1" className="text-sm">
|
|
||||||
{videoPreview && videoPreview !== ""
|
|
||||||
? "Видео выбрано"
|
|
||||||
: "Видео не выбрано"}
|
|
||||||
</Typography>
|
|
||||||
{videoPreview && videoPreview !== "" && (
|
|
||||||
<Box
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setVideoPreview("");
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
cursor: "pointer",
|
|
||||||
color: "#999",
|
|
||||||
"&:hover": {
|
|
||||||
color: "#666",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body1" className="text-lg font-bold">
|
|
||||||
×
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={() => setIsSelectVideoDialogOpen(true)}
|
|
||||||
startIcon={<Plus size={16} />}
|
|
||||||
sx={{ minWidth: "auto", px: 2 }}
|
|
||||||
>
|
|
||||||
Выбрать
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<FormControl fullWidth required>
|
<FormControl fullWidth required>
|
||||||
<InputLabel>Прямой/обратный маршрут</InputLabel>
|
<InputLabel>Прямой/обратный маршрут</InputLabel>
|
||||||
@@ -404,6 +380,7 @@ export const RouteCreatePage = observer(() => {
|
|||||||
value={scaleMax}
|
value={scaleMax}
|
||||||
onChange={(e) => setScaleMax(e.target.value)}
|
onChange={(e) => setScaleMax(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
className="w-full"
|
className="w-full"
|
||||||
label="Поворот"
|
label="Поворот"
|
||||||
@@ -440,23 +417,17 @@ export const RouteCreatePage = observer(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ArticleSelectOrCreateDialog
|
||||||
{/* Модальное окно выбора статьи */}
|
|
||||||
<SelectArticleModal
|
|
||||||
open={isSelectArticleDialogOpen}
|
open={isSelectArticleDialogOpen}
|
||||||
onClose={() => setIsSelectArticleDialogOpen(false)}
|
onClose={() => setIsSelectArticleDialogOpen(false)}
|
||||||
onSelectArticle={handleArticleSelect}
|
onSelectArticle={handleArticleSelect}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Модальное окно выбора видео */}
|
|
||||||
<SelectMediaDialog
|
<SelectMediaDialog
|
||||||
open={isSelectVideoDialogOpen}
|
open={isSelectVideoDialogOpen}
|
||||||
onClose={() => setIsSelectVideoDialogOpen(false)}
|
onClose={() => setIsSelectVideoDialogOpen(false)}
|
||||||
onSelectMedia={handleVideoSelect}
|
onSelectMedia={handleVideoSelect}
|
||||||
mediaType={2}
|
mediaType={2}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Модальное окно предпросмотра видео */}
|
|
||||||
{videoPreview && videoPreview !== "" && (
|
{videoPreview && videoPreview !== "" && (
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isVideoPreviewOpen}
|
open={isVideoPreviewOpen}
|
||||||
@@ -483,6 +454,18 @@ export const RouteCreatePage = observer(() => {
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
|
<UploadMediaDialog
|
||||||
|
open={isUploadVideoDialogOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsUploadVideoDialogOpen(false);
|
||||||
|
setFileToUpload(null);
|
||||||
|
}}
|
||||||
|
hardcodeType="video_preview"
|
||||||
|
contextObjectName={routeName || "Маршрут"}
|
||||||
|
contextType="sight"
|
||||||
|
initialFile={fileToUpload || undefined}
|
||||||
|
afterUpload={handleVideoUpload}
|
||||||
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogActions,
|
DialogActions,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { MediaViewer } from "@widgets";
|
import { MediaViewer, VideoPreviewCard } from "@widgets";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { ArrowLeft, Copy, Save, Plus } from "lucide-react";
|
import { ArrowLeft, Copy, Save, Plus } from "lucide-react";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
@@ -24,8 +24,9 @@ import { articlesStore } from "../../../shared/store/ArticlesStore";
|
|||||||
import {
|
import {
|
||||||
routeStore,
|
routeStore,
|
||||||
languageStore,
|
languageStore,
|
||||||
SelectArticleModal,
|
ArticleSelectOrCreateDialog,
|
||||||
SelectMediaDialog,
|
SelectMediaDialog,
|
||||||
|
UploadMediaDialog,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import { stationsStore } from "@shared";
|
import { stationsStore } from "@shared";
|
||||||
@@ -40,12 +41,13 @@ export const RouteEditPage = observer(() => {
|
|||||||
useState(false);
|
useState(false);
|
||||||
const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false);
|
const [isSelectVideoDialogOpen, setIsSelectVideoDialogOpen] = useState(false);
|
||||||
const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false);
|
const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false);
|
||||||
|
const [isUploadVideoDialogOpen, setIsUploadVideoDialogOpen] = useState(false);
|
||||||
|
const [fileToUpload, setFileToUpload] = useState<File | null>(null);
|
||||||
const { language } = languageStore;
|
const { language } = languageStore;
|
||||||
const [coordinates, setCoordinates] = useState<string>("");
|
const [coordinates, setCoordinates] = useState<string>("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
// Устанавливаем русский язык при загрузке страницы
|
|
||||||
const response = await routeStore.getRoute(Number(id));
|
const response = await routeStore.getRoute(Number(id));
|
||||||
routeStore.setEditRouteData(response);
|
routeStore.setEditRouteData(response);
|
||||||
languageStore.setLanguage("ru");
|
languageStore.setLanguage("ru");
|
||||||
@@ -125,6 +127,8 @@ export const RouteEditPage = observer(() => {
|
|||||||
governor_appeal: articleId,
|
governor_appeal: articleId,
|
||||||
});
|
});
|
||||||
setIsSelectArticleDialogOpen(false);
|
setIsSelectArticleDialogOpen(false);
|
||||||
|
// Обновляем список статей после создания новой
|
||||||
|
articlesStore.getArticleList();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVideoSelect = (media: {
|
const handleVideoSelect = (media: {
|
||||||
@@ -139,6 +143,28 @@ export const RouteEditPage = observer(() => {
|
|||||||
setIsSelectVideoDialogOpen(false);
|
setIsSelectVideoDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleVideoFileSelect = (file?: File) => {
|
||||||
|
if (file) {
|
||||||
|
setFileToUpload(file);
|
||||||
|
setIsUploadVideoDialogOpen(true);
|
||||||
|
} else {
|
||||||
|
setIsSelectVideoDialogOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVideoUpload = (media: {
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
media_name?: string;
|
||||||
|
media_type: number;
|
||||||
|
}) => {
|
||||||
|
routeStore.setEditRouteData({
|
||||||
|
video_preview: media.id,
|
||||||
|
});
|
||||||
|
setIsUploadVideoDialogOpen(false);
|
||||||
|
setFileToUpload(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleVideoPreviewClick = () => {
|
const handleVideoPreviewClick = () => {
|
||||||
if (editRouteData.video_preview && editRouteData.video_preview !== "") {
|
if (editRouteData.video_preview && editRouteData.video_preview !== "") {
|
||||||
setIsVideoPreviewOpen(true);
|
setIsVideoPreviewOpen(true);
|
||||||
@@ -164,6 +190,17 @@ export const RouteEditPage = observer(() => {
|
|||||||
|
|
||||||
<div className="flex flex-col gap-10 w-full items-end">
|
<div className="flex flex-col gap-10 w-full items-end">
|
||||||
<Box className="flex flex-col gap-6 w-full">
|
<Box className="flex flex-col gap-6 w-full">
|
||||||
|
<TextField
|
||||||
|
className="w-full"
|
||||||
|
label="Название маршрута"
|
||||||
|
required
|
||||||
|
value={editRouteData.route_name || ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
routeStore.setEditRouteData({
|
||||||
|
route_name: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
<FormControl fullWidth required>
|
<FormControl fullWidth required>
|
||||||
<InputLabel>Выберите перевозчика</InputLabel>
|
<InputLabel>Выберите перевозчика</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@@ -235,7 +272,6 @@ export const RouteEditPage = observer(() => {
|
|||||||
const lines = coordinates.split("\n");
|
const lines = coordinates.split("\n");
|
||||||
const lastLine = lines[lines.length - 1];
|
const lastLine = lines[lines.length - 1];
|
||||||
|
|
||||||
// Если мы на последней строке и она не пустая
|
|
||||||
if (lastLine && lastLine.trim()) {
|
if (lastLine && lastLine.trim()) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const newValue = coordinates + "\n";
|
const newValue = coordinates + "\n";
|
||||||
@@ -279,110 +315,6 @@ export const RouteEditPage = observer(() => {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Заменяем Select на кнопку для выбора статьи */}
|
|
||||||
<Box className="flex flex-col gap-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700">
|
|
||||||
Обращение к пассажирам
|
|
||||||
</label>
|
|
||||||
<Box className="flex gap-2">
|
|
||||||
<TextField
|
|
||||||
className="flex-1"
|
|
||||||
value={selectedArticle?.heading || "Статья не выбрана"}
|
|
||||||
placeholder="Выберите статью"
|
|
||||||
disabled
|
|
||||||
sx={{
|
|
||||||
"& .MuiInputBase-input": {
|
|
||||||
color: selectedArticle ? "inherit" : "#999",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={() => setIsSelectArticleDialogOpen(true)}
|
|
||||||
startIcon={<Plus size={16} />}
|
|
||||||
sx={{ minWidth: "auto", px: 2 }}
|
|
||||||
>
|
|
||||||
Выбрать
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Селектор видеозаставки */}
|
|
||||||
<Box className="flex flex-col gap-2">
|
|
||||||
<label className="text-sm font-medium text-gray-700">
|
|
||||||
Видеозаставка
|
|
||||||
</label>
|
|
||||||
<Box className="flex gap-2">
|
|
||||||
<Box
|
|
||||||
className="flex-1"
|
|
||||||
onClick={handleVideoPreviewClick}
|
|
||||||
sx={{
|
|
||||||
cursor:
|
|
||||||
editRouteData.video_preview &&
|
|
||||||
editRouteData.video_preview !== ""
|
|
||||||
? "pointer"
|
|
||||||
: "default",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box
|
|
||||||
className="w-full h-[50px] border border-gray-400 rounded-sm flex items-center justify-between px-4"
|
|
||||||
sx={{
|
|
||||||
"& .MuiInputBase-input": {
|
|
||||||
color:
|
|
||||||
editRouteData.video_preview &&
|
|
||||||
editRouteData.video_preview !== ""
|
|
||||||
? "inherit"
|
|
||||||
: "#999",
|
|
||||||
cursor:
|
|
||||||
editRouteData.video_preview &&
|
|
||||||
editRouteData.video_preview !== ""
|
|
||||||
? "pointer"
|
|
||||||
: "default",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography variant="body1" className="text-sm">
|
|
||||||
{editRouteData.video_preview &&
|
|
||||||
editRouteData.video_preview !== ""
|
|
||||||
? "Видео выбрано"
|
|
||||||
: "Видео не выбрано"}
|
|
||||||
</Typography>
|
|
||||||
{editRouteData.video_preview &&
|
|
||||||
editRouteData.video_preview !== "" && (
|
|
||||||
<Box
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
routeStore.setEditRouteData({ video_preview: "" });
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
cursor: "pointer",
|
|
||||||
color: "#999",
|
|
||||||
"&:hover": {
|
|
||||||
color: "#666",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
className="text-lg font-bold"
|
|
||||||
>
|
|
||||||
×
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
<Button
|
|
||||||
variant="outlined"
|
|
||||||
onClick={() => setIsSelectVideoDialogOpen(true)}
|
|
||||||
startIcon={<Plus size={16} />}
|
|
||||||
sx={{ minWidth: "auto", px: 2 }}
|
|
||||||
>
|
|
||||||
Выбрать
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<FormControl fullWidth required>
|
<FormControl fullWidth required>
|
||||||
<InputLabel>Прямой/обратный маршрут</InputLabel>
|
<InputLabel>Прямой/обратный маршрут</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@@ -453,6 +385,43 @@ export const RouteEditPage = observer(() => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<Typography variant="subtitle1" sx={{ fontWeight: 600 }}>
|
||||||
|
Обращение к пассажирам
|
||||||
|
</Typography>
|
||||||
|
<Box className="flex gap-2">
|
||||||
|
<TextField
|
||||||
|
className="flex-1"
|
||||||
|
value={selectedArticle?.heading || "Статья не выбрана"}
|
||||||
|
placeholder="Выберите статью"
|
||||||
|
disabled
|
||||||
|
fullWidth
|
||||||
|
sx={{
|
||||||
|
"& .MuiInputBase-input": {
|
||||||
|
color: selectedArticle ? "inherit" : "#999",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant="outlined"
|
||||||
|
onClick={() => setIsSelectArticleDialogOpen(true)}
|
||||||
|
startIcon={<Plus size={16} />}
|
||||||
|
sx={{ minWidth: "auto", px: 2 }}
|
||||||
|
>
|
||||||
|
Выбрать
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<VideoPreviewCard
|
||||||
|
title="Видеозаставка"
|
||||||
|
videoId={editRouteData.video_preview}
|
||||||
|
onVideoClick={handleVideoPreviewClick}
|
||||||
|
onDeleteVideoClick={() => {
|
||||||
|
routeStore.setEditRouteData({ video_preview: "" });
|
||||||
|
}}
|
||||||
|
onSelectVideoClick={handleVideoFileSelect}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<LinkedItems
|
<LinkedItems
|
||||||
@@ -493,23 +462,17 @@ export const RouteEditPage = observer(() => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<ArticleSelectOrCreateDialog
|
||||||
{/* Модальное окно выбора статьи */}
|
|
||||||
<SelectArticleModal
|
|
||||||
open={isSelectArticleDialogOpen}
|
open={isSelectArticleDialogOpen}
|
||||||
onClose={() => setIsSelectArticleDialogOpen(false)}
|
onClose={() => setIsSelectArticleDialogOpen(false)}
|
||||||
onSelectArticle={handleArticleSelect}
|
onSelectArticle={handleArticleSelect}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Модальное окно выбора видео */}
|
|
||||||
<SelectMediaDialog
|
<SelectMediaDialog
|
||||||
open={isSelectVideoDialogOpen}
|
open={isSelectVideoDialogOpen}
|
||||||
onClose={() => setIsSelectVideoDialogOpen(false)}
|
onClose={() => setIsSelectVideoDialogOpen(false)}
|
||||||
onSelectMedia={handleVideoSelect}
|
onSelectMedia={handleVideoSelect}
|
||||||
mediaType={2}
|
mediaType={2}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Модальное окно предпросмотра видео */}
|
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isVideoPreviewOpen}
|
open={isVideoPreviewOpen}
|
||||||
onClose={() => setIsVideoPreviewOpen(false)}
|
onClose={() => setIsVideoPreviewOpen(false)}
|
||||||
@@ -519,19 +482,33 @@ export const RouteEditPage = observer(() => {
|
|||||||
<DialogTitle>Предпросмотр видео</DialogTitle>
|
<DialogTitle>Предпросмотр видео</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box className="flex justify-center items-center p-4">
|
<Box className="flex justify-center items-center p-4">
|
||||||
<MediaViewer
|
{editRouteData.video_preview && (
|
||||||
media={{
|
<MediaViewer
|
||||||
id: editRouteData.video_preview,
|
media={{
|
||||||
media_type: 2,
|
id: editRouteData.video_preview,
|
||||||
filename: "video_preview",
|
media_type: 2,
|
||||||
}}
|
filename: "video_preview",
|
||||||
/>
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setIsVideoPreviewOpen(false)}>Закрыть</Button>
|
<Button onClick={() => setIsVideoPreviewOpen(false)}>Закрыть</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
<UploadMediaDialog
|
||||||
|
open={isUploadVideoDialogOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsUploadVideoDialogOpen(false);
|
||||||
|
setFileToUpload(null);
|
||||||
|
}}
|
||||||
|
hardcodeType="video_preview"
|
||||||
|
contextObjectName={editRouteData.route_name || "Маршрут"}
|
||||||
|
contextType="sight"
|
||||||
|
initialFile={fileToUpload || undefined}
|
||||||
|
afterUpload={handleVideoUpload}
|
||||||
|
/>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -50,6 +50,22 @@ export const RouteListPage = observer(() => {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
field: "route_name",
|
||||||
|
headerName: "Название маршрута",
|
||||||
|
flex: 1,
|
||||||
|
renderCell: (params: GridRenderCellParams) => {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-full flex items-center">
|
||||||
|
{params.value ? (
|
||||||
|
params.value
|
||||||
|
) : (
|
||||||
|
<Minus size={20} className="text-red-500" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
field: "route_number",
|
field: "route_number",
|
||||||
headerName: "Номер маршрута",
|
headerName: "Номер маршрута",
|
||||||
@@ -100,9 +116,7 @@ export const RouteListPage = observer(() => {
|
|||||||
<button onClick={() => navigate(`/route-preview/${params.row.id}`)}>
|
<button onClick={() => navigate(`/route-preview/${params.row.id}`)}>
|
||||||
<Map size={20} className="text-purple-500" />
|
<Map size={20} className="text-purple-500" />
|
||||||
</button>
|
</button>
|
||||||
{/* <button onClick={() => navigate(`/route/${params.row.id}`)}>
|
|
||||||
<Eye size={20} className="text-green-500" />
|
|
||||||
</button> */}
|
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsDeleteModalOpen(true);
|
setIsDeleteModalOpen(true);
|
||||||
@@ -122,6 +136,7 @@ export const RouteListPage = observer(() => {
|
|||||||
carrier_id: route.carrier_id,
|
carrier_id: route.carrier_id,
|
||||||
route_number: route.route_number,
|
route_number: route.route_number,
|
||||||
route_direction: route.route_direction ? "Прямой" : "Обратный",
|
route_direction: route.route_direction ? "Прямой" : "Обратный",
|
||||||
|
route_name: route.route_name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -71,10 +71,8 @@ export const clearBlobAndGLTFCache = async (url: string) => {
|
|||||||
*/
|
*/
|
||||||
export const clearMediaTransitionCache = async (
|
export const clearMediaTransitionCache = async (
|
||||||
previousMediaId: string | number | null,
|
previousMediaId: string | number | null,
|
||||||
newMediaId: string | number | null,
|
|
||||||
newMediaType?: number
|
newMediaType?: number
|
||||||
) => {
|
) => {
|
||||||
console.log(newMediaId, newMediaType);
|
|
||||||
// Если переключаемся с/на 3D модель, очищаем весь кеш
|
// Если переключаемся с/на 3D модель, очищаем весь кеш
|
||||||
if (newMediaType === 6 || previousMediaId) {
|
if (newMediaType === 6 || previousMediaId) {
|
||||||
await clearAllGLTFCache();
|
await clearAllGLTFCache();
|
||||||
|
|||||||
1070
src/shared/modals/ArticleSelectOrCreateDialog/index.tsx
Normal file
1070
src/shared/modals/ArticleSelectOrCreateDialog/index.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,3 +2,4 @@ export * from "./SelectArticleDialog";
|
|||||||
export * from "./SelectMediaDialog";
|
export * from "./SelectMediaDialog";
|
||||||
export * from "./PreviewMediaDialog";
|
export * from "./PreviewMediaDialog";
|
||||||
export * from "./UploadMediaDialog";
|
export * from "./UploadMediaDialog";
|
||||||
|
export * from "./ArticleSelectOrCreateDialog";
|
||||||
|
|||||||
@@ -340,55 +340,63 @@ class CreateSightStore {
|
|||||||
|
|
||||||
createLeftArticle = async () => {
|
createLeftArticle = async () => {
|
||||||
/* ... your existing logic to create a new left article (placeholder or DB) ... */
|
/* ... your existing logic to create a new left article (placeholder or DB) ... */
|
||||||
|
const ruName = (this.sight.ru.name || "").trim();
|
||||||
|
const enName = (this.sight.en.name || "").trim();
|
||||||
|
const zhName = (this.sight.zh.name || "").trim();
|
||||||
|
|
||||||
|
// If all names are empty, skip defaulting and use empty headings
|
||||||
|
const hasAnyName = !!(ruName || enName || zhName);
|
||||||
|
|
||||||
const response = await languageInstance("ru").post("/article", {
|
const response = await languageInstance("ru").post("/article", {
|
||||||
heading: "Новая левая статья",
|
heading: hasAnyName ? ruName : "",
|
||||||
body: "Заполните контентом",
|
body: "",
|
||||||
});
|
});
|
||||||
const newLeftArticleId = response.data.id;
|
const newLeftArticleId = response.data.id;
|
||||||
|
|
||||||
await languageInstance("en").patch(`/article/${newLeftArticleId}`, {
|
await languageInstance("en").patch(`/article/${newLeftArticleId}`, {
|
||||||
heading: "New Left Article",
|
heading: hasAnyName ? enName : "",
|
||||||
body: "Fill with content",
|
body: "",
|
||||||
});
|
});
|
||||||
await languageInstance("zh").patch(`/article/${newLeftArticleId}`, {
|
await languageInstance("zh").patch(`/article/${newLeftArticleId}`, {
|
||||||
heading: "新的左侧文章",
|
heading: hasAnyName ? zhName : "",
|
||||||
body: "填写内容",
|
body: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.sight.left_article = newLeftArticleId; // Store the actual ID
|
this.sight.left_article = newLeftArticleId; // Store the actual ID
|
||||||
this.sight.ru.left = {
|
this.sight.ru.left = {
|
||||||
heading: "Новая левая статья",
|
heading: hasAnyName ? ruName : "",
|
||||||
body: "Заполните контентом",
|
body: "",
|
||||||
media: [],
|
media: [],
|
||||||
};
|
};
|
||||||
this.sight.en.left = {
|
this.sight.en.left = {
|
||||||
heading: "New Left Article",
|
heading: hasAnyName ? enName : "",
|
||||||
body: "Fill with content",
|
body: "",
|
||||||
media: [],
|
media: [],
|
||||||
};
|
};
|
||||||
this.sight.zh.left = {
|
this.sight.zh.left = {
|
||||||
heading: "新的左侧文章",
|
heading: hasAnyName ? zhName : "",
|
||||||
body: "填写内容",
|
body: "",
|
||||||
media: [],
|
media: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
articlesStore.articles.ru.push({
|
articlesStore.articles.ru.push({
|
||||||
id: newLeftArticleId,
|
id: newLeftArticleId,
|
||||||
heading: "Новая левая статья",
|
heading: hasAnyName ? ruName : "",
|
||||||
body: "Заполните контентом",
|
body: "",
|
||||||
service_name: "Новая левая статья",
|
service_name: hasAnyName ? ruName : "",
|
||||||
});
|
});
|
||||||
articlesStore.articles.en.push({
|
articlesStore.articles.en.push({
|
||||||
id: newLeftArticleId,
|
id: newLeftArticleId,
|
||||||
heading: "New Left Article",
|
heading: hasAnyName ? enName : "",
|
||||||
body: "Fill with content",
|
body: "",
|
||||||
service_name: "New Left Article",
|
service_name: hasAnyName ? enName : "",
|
||||||
});
|
});
|
||||||
articlesStore.articles.zh.push({
|
articlesStore.articles.zh.push({
|
||||||
id: newLeftArticleId,
|
id: newLeftArticleId,
|
||||||
heading: "新的左侧文章",
|
heading: hasAnyName ? zhName : "",
|
||||||
body: "填写内容",
|
body: "",
|
||||||
service_name: "新的左侧文章",
|
service_name: hasAnyName ? zhName : "",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return newLeftArticleId;
|
return newLeftArticleId;
|
||||||
|
|||||||
@@ -400,16 +400,36 @@ class EditSightStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
createLeftArticle = async () => {
|
createLeftArticle = async () => {
|
||||||
|
const ruName = (this.sight.ru.name || "").trim();
|
||||||
|
const enName = (this.sight.en.name || "").trim();
|
||||||
|
const zhName = (this.sight.zh.name || "").trim();
|
||||||
|
const hasAnyName = !!(ruName || enName || zhName);
|
||||||
|
|
||||||
const response = await languageInstance("ru").post(`/article`, {
|
const response = await languageInstance("ru").post(`/article`, {
|
||||||
heading: "",
|
heading: hasAnyName ? ruName : "",
|
||||||
body: "",
|
body: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
this.sight.common.left_article = response.data.id;
|
this.sight.common.left_article = response.data.id;
|
||||||
|
|
||||||
this.sight.ru.left.heading = "";
|
await languageInstance("en").patch(
|
||||||
this.sight.en.left.heading = "";
|
`/article/${this.sight.common.left_article}`,
|
||||||
this.sight.zh.left.heading = "";
|
{
|
||||||
|
heading: hasAnyName ? enName : "",
|
||||||
|
body: "",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
await languageInstance("zh").patch(
|
||||||
|
`/article/${this.sight.common.left_article}`,
|
||||||
|
{
|
||||||
|
heading: hasAnyName ? zhName : "",
|
||||||
|
body: "",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.sight.ru.left.heading = hasAnyName ? ruName : "";
|
||||||
|
this.sight.en.left.heading = hasAnyName ? enName : "";
|
||||||
|
this.sight.zh.left.heading = hasAnyName ? zhName : "";
|
||||||
this.sight.ru.left.body = "";
|
this.sight.ru.left.body = "";
|
||||||
this.sight.en.left.body = "";
|
this.sight.en.left.body = "";
|
||||||
this.sight.zh.left.body = "";
|
this.sight.zh.left.body = "";
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { makeAutoObservable, runInAction } from "mobx";
|
|||||||
import { authInstance } from "@shared";
|
import { authInstance } from "@shared";
|
||||||
|
|
||||||
export type Route = {
|
export type Route = {
|
||||||
|
route_name: string;
|
||||||
carrier: string;
|
carrier: string;
|
||||||
carrier_id: number;
|
carrier_id: number;
|
||||||
center_latitude: number;
|
center_latitude: number;
|
||||||
@@ -97,6 +98,7 @@ class RouteStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
editRouteData = {
|
editRouteData = {
|
||||||
|
route_name: "",
|
||||||
carrier: "",
|
carrier: "",
|
||||||
carrier_id: 0,
|
carrier_id: 0,
|
||||||
center_latitude: "",
|
center_latitude: "",
|
||||||
@@ -110,7 +112,7 @@ class RouteStore {
|
|||||||
route_sys_number: "",
|
route_sys_number: "",
|
||||||
scale_max: 0,
|
scale_max: 0,
|
||||||
scale_min: 0,
|
scale_min: 0,
|
||||||
video_preview: "",
|
video_preview: "" as string | undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
setEditRouteData = (data: any) => {
|
setEditRouteData = (data: any) => {
|
||||||
@@ -118,6 +120,9 @@ class RouteStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
editRoute = async (id: number) => {
|
editRoute = async (id: number) => {
|
||||||
|
if (!this.editRouteData.video_preview) {
|
||||||
|
delete this.editRouteData.video_preview;
|
||||||
|
}
|
||||||
const response = await authInstance.patch(`/route/${id}`, {
|
const response = await authInstance.patch(`/route/${id}`, {
|
||||||
...this.editRouteData,
|
...this.editRouteData,
|
||||||
center_latitude: parseFloat(this.editRouteData.center_latitude),
|
center_latitude: parseFloat(this.editRouteData.center_latitude),
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useRef, useState, DragEvent, useEffect } from "react";
|
import React, { useRef, DragEvent } from "react";
|
||||||
import { Paper, Box, Typography, Button, Tooltip } from "@mui/material";
|
import { Paper, Box, Typography, Button, Tooltip } from "@mui/material";
|
||||||
import { X, Info, Plus } from "lucide-react"; // Assuming lucide-react for icons
|
import { X, Info, Plus } from "lucide-react"; // Assuming lucide-react for icons
|
||||||
import { editSightStore } from "@shared";
|
import { editSightStore } from "@shared";
|
||||||
@@ -27,18 +27,9 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
|
|||||||
tooltipText,
|
tooltipText,
|
||||||
}) => {
|
}) => {
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
|
||||||
const { setFileToUpload } = editSightStore;
|
const { setFileToUpload } = editSightStore;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isDragOver) {
|
|
||||||
console.log("isDragOver");
|
|
||||||
}
|
|
||||||
}, [isDragOver]);
|
|
||||||
|
|
||||||
// --- Click to select file ---
|
|
||||||
const handleZoneClick = () => {
|
const handleZoneClick = () => {
|
||||||
// Trigger the hidden file input click
|
|
||||||
fileInputRef.current?.click();
|
fileInputRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,19 +59,16 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
|
|||||||
const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
|
const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault(); // Crucial to allow a drop
|
event.preventDefault(); // Crucial to allow a drop
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setIsDragOver(true);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDragLeave = (event: DragEvent<HTMLDivElement>) => {
|
const handleDragLeave = (event: DragEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setIsDragOver(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDrop = async (event: DragEvent<HTMLDivElement>) => {
|
const handleDrop = async (event: DragEvent<HTMLDivElement>) => {
|
||||||
event.preventDefault(); // Crucial to allow a drop
|
event.preventDefault(); // Crucial to allow a drop
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
setIsDragOver(false);
|
|
||||||
|
|
||||||
const files = event.dataTransfer.files;
|
const files = event.dataTransfer.files;
|
||||||
if (files && files.length > 0) {
|
if (files && files.length > 0) {
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export function MediaViewer({
|
|||||||
// Используем новый cache manager для очистки кеша
|
// Используем новый cache manager для очистки кеша
|
||||||
clearMediaTransitionCache(
|
clearMediaTransitionCache(
|
||||||
previousMediaId,
|
previousMediaId,
|
||||||
media?.id || null,
|
|
||||||
media?.media_type
|
media?.media_type
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ interface VideoPreviewCardProps {
|
|||||||
onDeleteVideoClick: () => void;
|
onDeleteVideoClick: () => void;
|
||||||
onSelectVideoClick: (file?: File) => void;
|
onSelectVideoClick: (file?: File) => void;
|
||||||
tooltipText?: string;
|
tooltipText?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({
|
export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({
|
||||||
@@ -20,6 +21,7 @@ export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({
|
|||||||
onDeleteVideoClick,
|
onDeleteVideoClick,
|
||||||
onSelectVideoClick,
|
onSelectVideoClick,
|
||||||
tooltipText,
|
tooltipText,
|
||||||
|
className,
|
||||||
}) => {
|
}) => {
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
@@ -89,7 +91,10 @@ export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({
|
|||||||
gap: 1,
|
gap: 1,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
|
width: "min-content",
|
||||||
|
mx: "auto",
|
||||||
}}
|
}}
|
||||||
|
className={className}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
<Typography variant="subtitle2" gutterBottom sx={{ mb: 0, mr: 0.5 }}>
|
<Typography variant="subtitle2" gutterBottom sx={{ mb: 0, mr: 0.5 }}>
|
||||||
@@ -127,7 +132,10 @@ export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({
|
|||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{videoId ? (
|
{videoId ? (
|
||||||
<Box sx={{ position: "relative", width: "100%", height: "100%" }}>
|
<Box
|
||||||
|
sx={{ position: "relative", width: "100%", height: "100%" }}
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
<video
|
<video
|
||||||
src={`${
|
src={`${
|
||||||
import.meta.env.VITE_KRBL_MEDIA
|
import.meta.env.VITE_KRBL_MEDIA
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user