feat: Add scale
on group click, add cache for map entities
, fix map preview loading
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useTransform } from "./TransformContext";
|
import { useTransform } from "./TransformContext";
|
||||||
import { coordinatesToLocal, localToCoordinates } from "./utils";
|
import { coordinatesToLocal, localToCoordinates } from "./utils";
|
||||||
import { SCALE_FACTOR } from "./Constants";
|
import { SCALE_FACTOR } from "./Constants";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
export function RightSidebar() {
|
export function RightSidebar() {
|
||||||
const {
|
const {
|
||||||
@ -360,8 +361,14 @@ export function RightSidebar() {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
onClick={() => {
|
onClick={async () => {
|
||||||
saveChanges();
|
try {
|
||||||
|
await saveChanges();
|
||||||
|
toast.success("Изменения сохранены");
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error("Ошибка при сохранении изменений");
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Сохранить изменения
|
Сохранить изменения
|
||||||
|
@ -26,6 +26,7 @@ import { Sight } from "./Sight";
|
|||||||
import { SightData } from "./types";
|
import { SightData } from "./types";
|
||||||
import { Station } from "./Station";
|
import { Station } from "./Station";
|
||||||
import { UP_SCALE } from "./Constants";
|
import { UP_SCALE } from "./Constants";
|
||||||
|
import CircularProgress from "@mui/material/CircularProgress";
|
||||||
|
|
||||||
extend({
|
extend({
|
||||||
Container,
|
Container,
|
||||||
@ -36,13 +37,27 @@ extend({
|
|||||||
Text,
|
Text,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const Loading = () => {
|
||||||
|
const { isRouteLoading, isStationLoading, isSightLoading } = useMapData();
|
||||||
|
|
||||||
|
if (isRouteLoading || isStationLoading || isSightLoading) {
|
||||||
|
return (
|
||||||
|
<div className="fixed flex z-1 items-center justify-center h-screen w-screen bg-[#111]">
|
||||||
|
<CircularProgress />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
export const RoutePreview = () => {
|
export const RoutePreview = () => {
|
||||||
|
const { routeData, stationData, sightData } = useMapData();
|
||||||
return (
|
return (
|
||||||
<MapDataProvider>
|
<MapDataProvider>
|
||||||
<TransformProvider>
|
<TransformProvider>
|
||||||
<Stack direction="row" height="100vh" width="100vw" overflow="hidden">
|
<Stack direction="row" height="100vh" width="100vw" overflow="hidden">
|
||||||
<LanguageSwitcher />
|
{routeData && stationData && sightData ? <LanguageSwitcher /> : null}
|
||||||
|
<Loading />
|
||||||
<LeftSidebar />
|
<LeftSidebar />
|
||||||
<Stack direction="row" flex={1} position="relative" height="100%">
|
<Stack direction="row" flex={1} position="relative" height="100%">
|
||||||
<RouteMap />
|
<RouteMap />
|
||||||
@ -145,8 +160,7 @@ export const RouteMap = observer(() => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
if (!routeData || !stationData || !sightData) {
|
if (!routeData || !stationData || !sightData) {
|
||||||
console.error("routeData, stationData or sightData is null");
|
return null;
|
||||||
return <div>Loading...</div>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -81,6 +81,7 @@ export const UploadMediaDialog = observer(
|
|||||||
const [availableMediaTypes, setAvailableMediaTypes] = useState<number[]>(
|
const [availableMediaTypes, setAvailableMediaTypes] = useState<number[]>(
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
const [isPreviewLoaded, setIsPreviewLoaded] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialFile) {
|
if (initialFile) {
|
||||||
@ -207,6 +208,7 @@ export const UploadMediaDialog = observer(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mediaFile) {
|
if (mediaFile) {
|
||||||
setMediaUrl(URL.createObjectURL(mediaFile as Blob));
|
setMediaUrl(URL.createObjectURL(mediaFile as Blob));
|
||||||
|
setIsPreviewLoaded(false); // Сбрасываем состояние загрузки при смене файла
|
||||||
}
|
}
|
||||||
}, [mediaFile]);
|
}, [mediaFile]);
|
||||||
|
|
||||||
@ -326,8 +328,22 @@ export const UploadMediaDialog = observer(
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
height: "100%",
|
height: "100%",
|
||||||
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{!isPreviewLoaded && mediaUrl && (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(-50%, -50%)",
|
||||||
|
zIndex: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
{mediaType == 2 && mediaUrl && (
|
{mediaType == 2 && mediaUrl && (
|
||||||
<video
|
<video
|
||||||
src={mediaUrl}
|
src={mediaUrl}
|
||||||
@ -336,10 +352,16 @@ export const UploadMediaDialog = observer(
|
|||||||
loop
|
loop
|
||||||
controls
|
controls
|
||||||
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
style={{ maxWidth: "100%", maxHeight: "100%" }}
|
||||||
|
onLoadedData={() => setIsPreviewLoaded(true)}
|
||||||
|
onError={() => setIsPreviewLoaded(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{mediaType === 6 && mediaUrl && (
|
{mediaType === 6 && mediaUrl && (
|
||||||
<ModelViewer3D fileUrl={mediaUrl} height="100%" />
|
<ModelViewer3D
|
||||||
|
fileUrl={mediaUrl}
|
||||||
|
height="100%"
|
||||||
|
onLoad={() => setIsPreviewLoaded(true)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{mediaType !== 6 && mediaType !== 2 && mediaUrl && (
|
{mediaType !== 6 && mediaType !== 2 && mediaUrl && (
|
||||||
<img
|
<img
|
||||||
@ -349,6 +371,8 @@ export const UploadMediaDialog = observer(
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
objectFit: "contain",
|
objectFit: "contain",
|
||||||
}}
|
}}
|
||||||
|
onLoad={() => setIsPreviewLoaded(true)}
|
||||||
|
onError={() => setIsPreviewLoaded(true)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
@ -370,9 +394,17 @@ export const UploadMediaDialog = observer(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isLoading || (!mediaName && !mediaFilename)}
|
disabled={
|
||||||
|
isLoading ||
|
||||||
|
(!mediaName && !mediaFilename) ||
|
||||||
|
!isPreviewLoaded
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{isLoading ? "Сохранение..." : "Сохранить"}
|
{isLoading
|
||||||
|
? "Сохранение..."
|
||||||
|
: !isPreviewLoaded
|
||||||
|
? "Загрузка превью..."
|
||||||
|
: "Сохранить"}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -97,15 +97,19 @@ class SightsStore {
|
|||||||
city: number,
|
city: number,
|
||||||
coordinates: { latitude: number; longitude: number }
|
coordinates: { latitude: number; longitude: number }
|
||||||
) => {
|
) => {
|
||||||
const id = (
|
const response = await authInstance.post("/sight", {
|
||||||
await authInstance.post("/sight", {
|
|
||||||
name: this.createSight[languageStore.language].name,
|
name: this.createSight[languageStore.language].name,
|
||||||
address: this.createSight[languageStore.language].address,
|
address: this.createSight[languageStore.language].address,
|
||||||
city_id: city,
|
city_id: city,
|
||||||
latitude: coordinates.latitude,
|
latitude: coordinates.latitude,
|
||||||
longitude: coordinates.longitude,
|
longitude: coordinates.longitude,
|
||||||
})
|
});
|
||||||
).data.id;
|
|
||||||
|
runInAction(() => {
|
||||||
|
this.sights.push(response.data);
|
||||||
|
});
|
||||||
|
|
||||||
|
const id = response.data.id;
|
||||||
|
|
||||||
const anotherLanguages = ["ru", "en", "zh"].filter(
|
const anotherLanguages = ["ru", "en", "zh"].filter(
|
||||||
(language) => language !== languageStore.language
|
(language) => language !== languageStore.language
|
||||||
|
@ -51,7 +51,7 @@ export const LanguageSwitcher = observer(() => {
|
|||||||
key={lang}
|
key={lang}
|
||||||
onClick={() => handleLanguageChange(lang)}
|
onClick={() => handleLanguageChange(lang)}
|
||||||
variant={"contained"} // Highlight the active language
|
variant={"contained"} // Highlight the active language
|
||||||
color={language === lang ? "primary" : "secondary"}
|
color={language === lang ? "primary" : "inherit"}
|
||||||
sx={{ minWidth: "60px" }} // Give buttons a consistent width
|
sx={{ minWidth: "60px" }} // Give buttons a consistent width
|
||||||
>
|
>
|
||||||
{getLanguageLabel(lang)}
|
{getLanguageLabel(lang)}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import React from "react";
|
||||||
import { Stage, useGLTF } from "@react-three/drei";
|
import { Stage, useGLTF } from "@react-three/drei";
|
||||||
import { Canvas } from "@react-three/fiber";
|
import { Canvas } from "@react-three/fiber";
|
||||||
import { OrbitControls } from "@react-three/drei";
|
import { OrbitControls } from "@react-three/drei";
|
||||||
@ -5,12 +6,21 @@ import { OrbitControls } from "@react-three/drei";
|
|||||||
export const ModelViewer3D = ({
|
export const ModelViewer3D = ({
|
||||||
fileUrl,
|
fileUrl,
|
||||||
height = "100%",
|
height = "100%",
|
||||||
|
onLoad,
|
||||||
}: {
|
}: {
|
||||||
fileUrl: string;
|
fileUrl: string;
|
||||||
height: string;
|
height: string;
|
||||||
|
onLoad?: () => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { scene } = useGLTF(fileUrl);
|
const { scene } = useGLTF(fileUrl);
|
||||||
|
|
||||||
|
// Вызываем onLoad когда модель загружена
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (onLoad) {
|
||||||
|
onLoad();
|
||||||
|
}
|
||||||
|
}, [scene, onLoad]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Canvas style={{ width: "100%", height: height }}>
|
<Canvas style={{ width: "100%", height: height }}>
|
||||||
<ambientLight />
|
<ambientLight />
|
||||||
|
Reference in New Issue
Block a user