feat: Add preview_video
for sights
This commit is contained in:
@ -50,7 +50,6 @@ export const CityListPage = observer(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setRows(newRows2 || []);
|
setRows(newRows2 || []);
|
||||||
console.log(newRows2);
|
|
||||||
}, [cities, countryStore.countries, language, isLoading]);
|
}, [cities, countryStore.countries, language, isLoading]);
|
||||||
|
|
||||||
const columns: GridColDef[] = [
|
const columns: GridColDef[] = [
|
||||||
|
@ -17,6 +17,7 @@ export const MEDIA_TYPE_VALUES = {
|
|||||||
watermark_rd: 4,
|
watermark_rd: 4,
|
||||||
panorama: 5,
|
panorama: 5,
|
||||||
model: 6,
|
model: 6,
|
||||||
|
video_preview: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RU_COUNTRIES = [
|
export const RU_COUNTRIES = [
|
||||||
|
@ -36,7 +36,13 @@ interface UploadMediaDialogProps {
|
|||||||
media_type: number;
|
media_type: number;
|
||||||
}) => void;
|
}) => void;
|
||||||
afterUploadSight?: (id: string) => void;
|
afterUploadSight?: (id: string) => void;
|
||||||
hardcodeType?: "thumbnail" | "watermark_lu" | "watermark_rd" | "image" | null;
|
hardcodeType?:
|
||||||
|
| "thumbnail"
|
||||||
|
| "watermark_lu"
|
||||||
|
| "watermark_rd"
|
||||||
|
| "image"
|
||||||
|
| "video_preview"
|
||||||
|
| null;
|
||||||
contextObjectName?: string;
|
contextObjectName?: string;
|
||||||
contextType?:
|
contextType?:
|
||||||
| "sight"
|
| "sight"
|
||||||
@ -47,6 +53,7 @@ interface UploadMediaDialogProps {
|
|||||||
| "station";
|
| "station";
|
||||||
isArticle?: boolean;
|
isArticle?: boolean;
|
||||||
articleName?: string;
|
articleName?: string;
|
||||||
|
initialFile?: File; // <--- добавлено
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UploadMediaDialog = observer(
|
export const UploadMediaDialog = observer(
|
||||||
@ -60,6 +67,7 @@ export const UploadMediaDialog = observer(
|
|||||||
|
|
||||||
isArticle,
|
isArticle,
|
||||||
articleName,
|
articleName,
|
||||||
|
initialFile, // <--- добавлено
|
||||||
}: UploadMediaDialogProps) => {
|
}: UploadMediaDialogProps) => {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@ -74,6 +82,17 @@ export const UploadMediaDialog = observer(
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (initialFile) {
|
||||||
|
setMediaFile(initialFile);
|
||||||
|
setMediaFilename(initialFile.name);
|
||||||
|
setAvailableMediaTypes([2]);
|
||||||
|
setMediaType(2);
|
||||||
|
setMediaUrl(URL.createObjectURL(initialFile));
|
||||||
|
setMediaName(initialFile.name.replace(/\.[^/.]+$/, ""));
|
||||||
|
}
|
||||||
|
}, [initialFile]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (fileToUpload) {
|
if (fileToUpload) {
|
||||||
setMediaFile(fileToUpload);
|
setMediaFile(fileToUpload);
|
||||||
@ -226,6 +245,10 @@ export const UploadMediaDialog = observer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setSuccess(true);
|
setSuccess(true);
|
||||||
|
// Закрываем модальное окно после успешного сохранения
|
||||||
|
setTimeout(() => {
|
||||||
|
handleClose();
|
||||||
|
}, 1000); // Небольшая задержка, чтобы пользователь увидел сообщение об успехе
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "Failed to save media");
|
setError(err instanceof Error ? err.message : "Failed to save media");
|
||||||
} finally {
|
} finally {
|
||||||
@ -333,10 +356,15 @@ export const UploadMediaDialog = observer(
|
|||||||
<Box className="flex flex-col gap-2 self-end">
|
<Box className="flex flex-col gap-2 self-end">
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="success"
|
sx={{
|
||||||
|
backgroundColor: isLoading ? "#9e9e9e" : "#4caf50",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: isLoading ? "#9e9e9e" : "#45a049",
|
||||||
|
},
|
||||||
|
}}
|
||||||
startIcon={
|
startIcon={
|
||||||
isLoading ? (
|
isLoading ? (
|
||||||
<CircularProgress size={16} />
|
<CircularProgress size={16} color="inherit" />
|
||||||
) : (
|
) : (
|
||||||
<Save size={16} />
|
<Save size={16} />
|
||||||
)
|
)
|
||||||
@ -344,7 +372,7 @@ export const UploadMediaDialog = observer(
|
|||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isLoading || (!mediaName && !mediaFilename)}
|
disabled={isLoading || (!mediaName && !mediaFilename)}
|
||||||
>
|
>
|
||||||
Сохранить
|
{isLoading ? "Сохранение..." : "Сохранить"}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -12,6 +12,7 @@ class DevicesStore {
|
|||||||
|
|
||||||
getDevices = async () => {
|
getDevices = async () => {
|
||||||
const response = await authInstance.get(`${API_URL}/devices/connected`);
|
const response = await authInstance.get(`${API_URL}/devices/connected`);
|
||||||
|
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
this.devices = response.data;
|
this.devices = response.data;
|
||||||
});
|
});
|
||||||
|
@ -174,6 +174,7 @@ export const DevicesTable = observer(() => {
|
|||||||
setSelectedDevice(uuid); // Sets the device in the store, useful for context elsewhere
|
setSelectedDevice(uuid); // Sets the device in the store, useful for context elsewhere
|
||||||
try {
|
try {
|
||||||
await authInstance.post(`/devices/${uuid}/request-status`);
|
await authInstance.post(`/devices/${uuid}/request-status`);
|
||||||
|
await getVehicles();
|
||||||
await getDevices(); // Refresh devices to show updated status
|
await getDevices(); // Refresh devices to show updated status
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error requesting status for device ${uuid}:`, error);
|
console.error(`Error requesting status for device ${uuid}:`, error);
|
||||||
@ -398,7 +399,6 @@ export const DevicesTable = observer(() => {
|
|||||||
devices.find((device) => device === row.device_uuid)
|
devices.find((device) => device === row.device_uuid)
|
||||||
) {
|
) {
|
||||||
await handleReloadStatus(row.device_uuid);
|
await handleReloadStatus(row.device_uuid);
|
||||||
await getDevices();
|
|
||||||
toast.success("Статус устройства обновлен");
|
toast.success("Статус устройства обновлен");
|
||||||
} else {
|
} else {
|
||||||
toast.error("Нет связи с устройством");
|
toast.error("Нет связи с устройством");
|
||||||
|
@ -59,7 +59,6 @@ export const Layout: React.FC<LayoutProps> = observer(({ children }) => {
|
|||||||
<div className="flex gap-2 items-center">
|
<div className="flex gap-2 items-center">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{(() => {
|
{(() => {
|
||||||
console.log(authStore.payload);
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className=" text-white">
|
<p className=" text-white">
|
||||||
|
@ -5,6 +5,10 @@ import {
|
|||||||
Autocomplete,
|
Autocomplete,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Menu as MuiMenu,
|
Menu as MuiMenu,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
BackButton,
|
BackButton,
|
||||||
@ -20,7 +24,12 @@ import {
|
|||||||
UploadMediaDialog,
|
UploadMediaDialog,
|
||||||
MEDIA_TYPE_VALUES,
|
MEDIA_TYPE_VALUES,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
|
import {
|
||||||
|
ImageUploadCard,
|
||||||
|
LanguageSwitcher,
|
||||||
|
VideoPreviewCard,
|
||||||
|
MediaViewer,
|
||||||
|
} from "@widgets";
|
||||||
import { Save } from "lucide-react";
|
import { Save } from "lucide-react";
|
||||||
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
@ -45,13 +54,15 @@ export const CreateInformationTab = observer(
|
|||||||
// Menu state for each media button
|
// Menu state for each media button
|
||||||
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
|
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
const [activeMenuType, setActiveMenuType] = useState<
|
const [activeMenuType, setActiveMenuType] = useState<
|
||||||
"thumbnail" | "watermark_lu" | "watermark_rd" | null
|
"thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview" | null
|
||||||
>(null);
|
>(null);
|
||||||
const [isAddMediaOpen, setIsAddMediaOpen] = useState(false);
|
const [isAddMediaOpen, setIsAddMediaOpen] = useState(false);
|
||||||
|
const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false);
|
||||||
const [hardcodeType, setHardcodeType] = useState<
|
const [hardcodeType, setHardcodeType] = useState<
|
||||||
"thumbnail" | "watermark_lu" | "watermark_rd" | null
|
"thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview" | null
|
||||||
>(null);
|
>(null);
|
||||||
|
|
||||||
|
useEffect(() => {}, [hardcodeType]);
|
||||||
// const handleMenuOpen = (
|
// const handleMenuOpen = (
|
||||||
// event: React.MouseEvent<HTMLElement>,
|
// event: React.MouseEvent<HTMLElement>,
|
||||||
// type: "thumbnail" | "watermark_lu" | "watermark_rd"
|
// type: "thumbnail" | "watermark_lu" | "watermark_rd"
|
||||||
@ -100,7 +111,7 @@ export const CreateInformationTab = observer(
|
|||||||
media_name?: string;
|
media_name?: string;
|
||||||
media_type: number;
|
media_type: number;
|
||||||
},
|
},
|
||||||
type: "thumbnail" | "watermark_lu" | "watermark_rd"
|
type: "thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview"
|
||||||
) => {
|
) => {
|
||||||
handleChange({
|
handleChange({
|
||||||
[type]: media.id,
|
[type]: media.id,
|
||||||
@ -108,6 +119,12 @@ export const CreateInformationTab = observer(
|
|||||||
setActiveMenuType(null);
|
setActiveMenuType(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleVideoPreviewClick = () => {
|
||||||
|
if (sight.video_preview && sight.video_preview !== "") {
|
||||||
|
setIsVideoPreviewOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TabPanel value={value} index={index}>
|
<TabPanel value={value} index={index}>
|
||||||
@ -329,6 +346,29 @@ export const CreateInformationTab = observer(
|
|||||||
setHardcodeType("watermark_rd");
|
setHardcodeType("watermark_rd");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<VideoPreviewCard
|
||||||
|
title="Видео превью"
|
||||||
|
videoId={sight.video_preview}
|
||||||
|
onVideoClick={handleVideoPreviewClick}
|
||||||
|
onDeleteVideoClick={() => {
|
||||||
|
handleChange({
|
||||||
|
video_preview: null,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onSelectVideoClick={(file) => {
|
||||||
|
if (file) {
|
||||||
|
// Если передан файл, открываем диалог загрузки медиа
|
||||||
|
createSightStore.setFileToUpload(file);
|
||||||
|
setActiveMenuType("video_preview");
|
||||||
|
setIsUploadMediaOpen(true);
|
||||||
|
} else {
|
||||||
|
// Если файл не передан, открываем диалог выбора существующих медиа
|
||||||
|
setActiveMenuType("video_preview");
|
||||||
|
setIsAddMediaOpen(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -390,7 +430,9 @@ export const CreateInformationTab = observer(
|
|||||||
setActiveMenuType(null);
|
setActiveMenuType(null);
|
||||||
}}
|
}}
|
||||||
onSelectMedia={(media) => {
|
onSelectMedia={(media) => {
|
||||||
handleMediaSelect(media, activeMenuType ?? "thumbnail");
|
if (activeMenuType) {
|
||||||
|
handleMediaSelect(media, activeMenuType);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
mediaType={
|
mediaType={
|
||||||
activeMenuType
|
activeMenuType
|
||||||
@ -413,14 +455,49 @@ export const CreateInformationTab = observer(
|
|||||||
contextObjectName={sight[language].name}
|
contextObjectName={sight[language].name}
|
||||||
contextType="sight"
|
contextType="sight"
|
||||||
afterUpload={(media) => {
|
afterUpload={(media) => {
|
||||||
handleChange({
|
if (activeMenuType === "video_preview") {
|
||||||
[activeMenuType ?? "thumbnail"]: media.id,
|
handleChange({
|
||||||
});
|
video_preview: media.id,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
handleChange({
|
||||||
|
[activeMenuType ?? "thumbnail"]: media.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
setActiveMenuType(null);
|
setActiveMenuType(null);
|
||||||
setIsUploadMediaOpen(false);
|
setIsUploadMediaOpen(false);
|
||||||
}}
|
}}
|
||||||
hardcodeType={hardcodeType}
|
hardcodeType={activeMenuType}
|
||||||
|
initialFile={createSightStore.fileToUpload || undefined}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Модальное окно предпросмотра видео */}
|
||||||
|
{sight.video_preview && sight.video_preview !== "" && (
|
||||||
|
<Dialog
|
||||||
|
open={isVideoPreviewOpen}
|
||||||
|
onClose={() => setIsVideoPreviewOpen(false)}
|
||||||
|
maxWidth="md"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<DialogTitle>Предпросмотр видео</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box className="flex justify-center items-center p-4">
|
||||||
|
<MediaViewer
|
||||||
|
media={{
|
||||||
|
id: sight.video_preview,
|
||||||
|
media_type: 2,
|
||||||
|
filename: "video_preview",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setIsVideoPreviewOpen(false)}>
|
||||||
|
Закрыть
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,10 @@ import {
|
|||||||
Autocomplete,
|
Autocomplete,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Menu as MuiMenu,
|
Menu as MuiMenu,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogActions,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import {
|
import {
|
||||||
BackButton,
|
BackButton,
|
||||||
@ -20,7 +24,12 @@ import {
|
|||||||
UploadMediaDialog,
|
UploadMediaDialog,
|
||||||
MEDIA_TYPE_VALUES,
|
MEDIA_TYPE_VALUES,
|
||||||
} from "@shared";
|
} from "@shared";
|
||||||
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
|
import {
|
||||||
|
ImageUploadCard,
|
||||||
|
LanguageSwitcher,
|
||||||
|
VideoPreviewCard,
|
||||||
|
MediaViewer,
|
||||||
|
} from "@widgets";
|
||||||
import { Save } from "lucide-react";
|
import { Save } from "lucide-react";
|
||||||
|
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
@ -45,13 +54,17 @@ export const InformationTab = observer(
|
|||||||
// Menu state for each media button
|
// Menu state for each media button
|
||||||
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
|
const [menuAnchorEl, setMenuAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
const [activeMenuType, setActiveMenuType] = useState<
|
const [activeMenuType, setActiveMenuType] = useState<
|
||||||
"thumbnail" | "watermark_lu" | "watermark_rd" | null
|
"thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview" | null
|
||||||
>(null);
|
>(null);
|
||||||
const [isAddMediaOpen, setIsAddMediaOpen] = useState(false);
|
const [isAddMediaOpen, setIsAddMediaOpen] = useState(false);
|
||||||
|
const [isVideoPreviewOpen, setIsVideoPreviewOpen] = useState(false);
|
||||||
const [hardcodeType, setHardcodeType] = useState<
|
const [hardcodeType, setHardcodeType] = useState<
|
||||||
"thumbnail" | "watermark_lu" | "watermark_rd" | null
|
"thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview" | null
|
||||||
>(null);
|
>(null);
|
||||||
const { cities } = cityStore;
|
const { cities } = cityStore;
|
||||||
|
|
||||||
|
useEffect(() => {}, [hardcodeType]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Показывать только при инициализации (не менять при ошибках пользователя)
|
// Показывать только при инициализации (не менять при ошибках пользователя)
|
||||||
if (sight.common.latitude !== 0 || sight.common.longitude !== 0) {
|
if (sight.common.latitude !== 0 || sight.common.longitude !== 0) {
|
||||||
@ -74,22 +87,28 @@ export const InformationTab = observer(
|
|||||||
handleMenuClose();
|
handleMenuClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMediaSelect = (media: {
|
const handleMediaSelect = (
|
||||||
id: string;
|
media: {
|
||||||
filename: string;
|
id: string;
|
||||||
media_name?: string;
|
filename: string;
|
||||||
media_type: number;
|
media_name?: string;
|
||||||
}) => {
|
media_type: number;
|
||||||
if (!activeMenuType) return;
|
},
|
||||||
|
type: "thumbnail" | "watermark_lu" | "watermark_rd" | "video_preview"
|
||||||
|
) => {
|
||||||
handleChange(
|
handleChange(
|
||||||
language as Language,
|
language as Language,
|
||||||
{
|
{
|
||||||
[activeMenuType ?? "thumbnail"]: media.id,
|
[type]: media.id,
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
setIsUploadMediaOpen(false);
|
const handleVideoPreviewClick = () => {
|
||||||
|
if (sight.common.video_preview && sight.common.video_preview !== "") {
|
||||||
|
setIsVideoPreviewOpen(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (
|
const handleChange = (
|
||||||
@ -337,6 +356,33 @@ export const InformationTab = observer(
|
|||||||
setHardcodeType("watermark_rd");
|
setHardcodeType("watermark_rd");
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<VideoPreviewCard
|
||||||
|
title="Видео превью"
|
||||||
|
videoId={sight.common.video_preview}
|
||||||
|
onVideoClick={handleVideoPreviewClick}
|
||||||
|
onDeleteVideoClick={() => {
|
||||||
|
handleChange(
|
||||||
|
language as Language,
|
||||||
|
{
|
||||||
|
video_preview: null,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
onSelectVideoClick={(file) => {
|
||||||
|
if (file) {
|
||||||
|
// Если передан файл, открываем диалог загрузки медиа
|
||||||
|
editSightStore.setFileToUpload(file);
|
||||||
|
setActiveMenuType("video_preview");
|
||||||
|
setIsUploadMediaOpen(true);
|
||||||
|
} else {
|
||||||
|
// Если файл не передан, открываем диалог выбора существующих медиа
|
||||||
|
setActiveMenuType("video_preview");
|
||||||
|
setIsAddMediaOpen(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
@ -395,8 +441,13 @@ export const InformationTab = observer(
|
|||||||
open={isAddMediaOpen}
|
open={isAddMediaOpen}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
setIsAddMediaOpen(false);
|
setIsAddMediaOpen(false);
|
||||||
|
setActiveMenuType(null);
|
||||||
|
}}
|
||||||
|
onSelectMedia={(media) => {
|
||||||
|
if (activeMenuType) {
|
||||||
|
handleMediaSelect(media, activeMenuType);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onSelectMedia={handleMediaSelect}
|
|
||||||
mediaType={
|
mediaType={
|
||||||
activeMenuType
|
activeMenuType
|
||||||
? MEDIA_TYPE_VALUES[
|
? MEDIA_TYPE_VALUES[
|
||||||
@ -412,23 +463,62 @@ export const InformationTab = observer(
|
|||||||
contextObjectName={sight[language].name}
|
contextObjectName={sight[language].name}
|
||||||
contextType="sight"
|
contextType="sight"
|
||||||
afterUpload={(media) => {
|
afterUpload={(media) => {
|
||||||
handleChange(
|
if (activeMenuType === "video_preview") {
|
||||||
language as Language,
|
handleChange(
|
||||||
{
|
language as Language,
|
||||||
[activeMenuType ?? "thumbnail"]: media.id,
|
{
|
||||||
},
|
video_preview: media.id,
|
||||||
true
|
},
|
||||||
);
|
true
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
handleChange(
|
||||||
|
language as Language,
|
||||||
|
{
|
||||||
|
[activeMenuType ?? "thumbnail"]: media.id,
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
setActiveMenuType(null);
|
setActiveMenuType(null);
|
||||||
setIsUploadMediaOpen(false);
|
setIsUploadMediaOpen(false);
|
||||||
}}
|
}}
|
||||||
hardcodeType={hardcodeType}
|
hardcodeType={activeMenuType}
|
||||||
|
initialFile={editSightStore.fileToUpload || undefined}
|
||||||
/>
|
/>
|
||||||
<PreviewMediaDialog
|
<PreviewMediaDialog
|
||||||
open={isPreviewMediaOpen}
|
open={isPreviewMediaOpen}
|
||||||
onClose={() => setIsPreviewMediaOpen(false)}
|
onClose={() => setIsPreviewMediaOpen(false)}
|
||||||
mediaId={mediaId}
|
mediaId={mediaId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Модальное окно предпросмотра видео */}
|
||||||
|
{sight.common.video_preview && sight.common.video_preview !== "" && (
|
||||||
|
<Dialog
|
||||||
|
open={isVideoPreviewOpen}
|
||||||
|
onClose={() => setIsVideoPreviewOpen(false)}
|
||||||
|
maxWidth="md"
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<DialogTitle>Предпросмотр видео</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<Box className="flex justify-center items-center p-4">
|
||||||
|
<MediaViewer
|
||||||
|
media={{
|
||||||
|
id: sight.common.video_preview,
|
||||||
|
media_type: 2,
|
||||||
|
filename: "video_preview",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setIsVideoPreviewOpen(false)}>
|
||||||
|
Закрыть
|
||||||
|
</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
203
src/widgets/VideoPreviewCard/index.tsx
Normal file
203
src/widgets/VideoPreviewCard/index.tsx
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import React, { useRef, useState, DragEvent, useEffect } from "react";
|
||||||
|
import { Paper, Box, Typography, Button, Tooltip } from "@mui/material";
|
||||||
|
import { X, Info, Plus, Play } from "lucide-react";
|
||||||
|
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
interface VideoPreviewCardProps {
|
||||||
|
title: string;
|
||||||
|
videoId: string | null | undefined;
|
||||||
|
onVideoClick: () => void;
|
||||||
|
onDeleteVideoClick: () => void;
|
||||||
|
onSelectVideoClick: (file?: File) => void;
|
||||||
|
tooltipText?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VideoPreviewCard: React.FC<VideoPreviewCardProps> = ({
|
||||||
|
title,
|
||||||
|
videoId,
|
||||||
|
onVideoClick,
|
||||||
|
onDeleteVideoClick,
|
||||||
|
onSelectVideoClick,
|
||||||
|
tooltipText,
|
||||||
|
}) => {
|
||||||
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
|
useEffect(() => {}, [isDragOver]);
|
||||||
|
// --- Click to select file ---
|
||||||
|
const handleZoneClick = () => {
|
||||||
|
// Trigger the hidden file input click
|
||||||
|
fileInputRef.current?.click();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileInputChange = async (
|
||||||
|
event: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
if (file.type.startsWith("video/")) {
|
||||||
|
// Открываем диалог загрузки медиа с файлом видео
|
||||||
|
onSelectVideoClick(file);
|
||||||
|
} else {
|
||||||
|
toast.error("Пожалуйста, выберите видео файл");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reset the input value so selecting the same file again triggers change
|
||||||
|
event.target.value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Drag and Drop Handlers ---
|
||||||
|
const handleDragOver = (event: DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault(); // Crucial to allow a drop
|
||||||
|
event.stopPropagation();
|
||||||
|
setIsDragOver(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDragLeave = (event: DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
setIsDragOver(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDrop = async (event: DragEvent<HTMLDivElement>) => {
|
||||||
|
event.preventDefault(); // Crucial to allow a drop
|
||||||
|
event.stopPropagation();
|
||||||
|
setIsDragOver(false);
|
||||||
|
|
||||||
|
const files = event.dataTransfer.files;
|
||||||
|
if (files && files.length > 0) {
|
||||||
|
const file = files[0];
|
||||||
|
if (file.type.startsWith("video/")) {
|
||||||
|
// Открываем диалог загрузки медиа с файлом видео
|
||||||
|
onSelectVideoClick(file);
|
||||||
|
} else {
|
||||||
|
toast.error("Пожалуйста, выберите видео файл");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Paper
|
||||||
|
elevation={2}
|
||||||
|
sx={{
|
||||||
|
padding: 2,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 1,
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 150,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<Typography variant="subtitle2" gutterBottom sx={{ mb: 0, mr: 0.5 }}>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
{tooltipText && (
|
||||||
|
<Tooltip title={tooltipText}>
|
||||||
|
<Info size={16} color="gray" style={{ cursor: "pointer" }} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "relative",
|
||||||
|
width: "200px",
|
||||||
|
height: "200px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
borderRadius: 1,
|
||||||
|
mb: 1,
|
||||||
|
cursor: videoId ? "pointer" : "default",
|
||||||
|
}}
|
||||||
|
onClick={onVideoClick}
|
||||||
|
>
|
||||||
|
{videoId && (
|
||||||
|
<button
|
||||||
|
className="absolute top-2 right-2 z-10"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onDeleteVideoClick();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<X color="red" />
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{videoId ? (
|
||||||
|
<Box sx={{ position: "relative", width: "100%", height: "100%" }}>
|
||||||
|
<video
|
||||||
|
src={`${
|
||||||
|
import.meta.env.VITE_KRBL_MEDIA
|
||||||
|
}${videoId}/download?token=${token}`}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
borderRadius: 4,
|
||||||
|
}}
|
||||||
|
muted
|
||||||
|
/>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
backgroundColor: "rgba(0, 0, 0, 0.5)",
|
||||||
|
borderRadius: "50%",
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Play size={20} color="white" />
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<div className="w-full flex flex-col items-center justify-center gap-3">
|
||||||
|
<div
|
||||||
|
className="flex flex-col p-5 items-center justify-center gap-3"
|
||||||
|
style={{
|
||||||
|
border: "2px dashed #ccc",
|
||||||
|
borderRadius: 1,
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={handleZoneClick} // Click handler for the zone
|
||||||
|
onDragOver={handleDragOver}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDrop={handleDrop}
|
||||||
|
>
|
||||||
|
<p className="text-center">Перетащите файл</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>или</p>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<Plus color="white" size={18} />}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation(); // Prevent `handleZoneClick` from firing
|
||||||
|
onSelectVideoClick(); // This button triggers the media selection dialog
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Выбрать файл
|
||||||
|
</Button>
|
||||||
|
{/* Hidden file input */}
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
ref={fileInputRef}
|
||||||
|
onChange={handleFileInputChange}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
accept="video/*" // Accept only video files
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
};
|
@ -12,6 +12,7 @@ export * from "./MediaArea";
|
|||||||
export * from "./ModelViewer3D";
|
export * from "./ModelViewer3D";
|
||||||
export * from "./MediaAreaForSight";
|
export * from "./MediaAreaForSight";
|
||||||
export * from "./ImageUploadCard";
|
export * from "./ImageUploadCard";
|
||||||
|
export * from "./VideoPreviewCard";
|
||||||
export * from "./LeaveAgree";
|
export * from "./LeaveAgree";
|
||||||
export * from "./DeleteModal";
|
export * from "./DeleteModal";
|
||||||
export * from "./SnapshotRestore";
|
export * from "./SnapshotRestore";
|
||||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user