diff --git a/src/app/index.tsx b/src/app/index.tsx index 8568a98..67356e5 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -1,18 +1,25 @@ import * as React from "react"; import { Router } from "./router"; -import { CustomTheme } from "@shared"; +import { CustomTheme, languageStore } from "@shared"; import { ThemeProvider } from "@mui/material/styles"; import { ToastContainer } from "react-toastify"; import { GlobalErrorBoundary } from "./GlobalErrorBoundary"; import { TestingModeBanner } from "@widgets"; +import { observer } from "mobx-react-lite"; -export const App: React.FC = () => ( - - - - - - - -); +export const App: React.FC = observer(() => { + React.useEffect(() => { + document.documentElement.lang = languageStore.language; + }, [languageStore.language]); + + return ( + + + + + + + + ); +}); diff --git a/src/client/src/components/ListOfSights.jsx b/src/client/src/components/ListOfSights.jsx index 9475468..982ada3 100644 --- a/src/client/src/components/ListOfSights.jsx +++ b/src/client/src/components/ListOfSights.jsx @@ -411,7 +411,7 @@ const ListOfSights = observer(() => { }, [currentSelectedSight]); return ( -
+
{currentSelectedSight && ( +
{isLoading ? (
Загрузка информации...
) : error ? ( diff --git a/src/client/src/components/widgets/ThreeView.tsx b/src/client/src/components/widgets/ThreeView.tsx index 1ff998e..3eaaf41 100644 --- a/src/client/src/components/widgets/ThreeView.tsx +++ b/src/client/src/components/widgets/ThreeView.tsx @@ -1,6 +1,6 @@ -import { Canvas, useThree } from "@react-three/fiber"; -import { OrbitControls, Stage, useGLTF } from "@react-three/drei"; -import React, { useEffect, Suspense } from "react"; +import { Canvas, useThree, useFrame } from "@react-three/fiber"; +import { OrbitControls, Center, useGLTF } from "@react-three/drei"; +import React, { useEffect, useRef, Suspense, useCallback } from "react"; import { BACKGROUND_COLOR } from "../../assets/Constants"; import * as THREE from "three"; import type { OrbitControls as OrbitControlsImpl } from "three-stdlib"; @@ -23,6 +23,7 @@ interface ThreeViewProps { const ZOOM_FACTOR = 1.2; const MIN_DISTANCE = 1; const MAX_DISTANCE = 100; +const CAMERA_FOV = 40; const TouchController = () => { const { camera, controls, gl } = useThree(); @@ -197,6 +198,47 @@ const AutoResize = () => { return null; }; +const FitCamera = ({ + groupRef, + onReady, +}: { + groupRef: React.RefObject; + onReady: () => void; +}) => { + const { camera, controls } = useThree(); + const fitted = useRef(false); + + useFrame(() => { + if (fitted.current) return; + const group = groupRef.current; + if (!group || group.children.length === 0) return; + + const box = new THREE.Box3().setFromObject(group); + const sphere = new THREE.Sphere(); + box.getBoundingSphere(sphere); + + if (sphere.radius === 0) return; + + const fov = THREE.MathUtils.degToRad(CAMERA_FOV); + const dist = sphere.radius / Math.sin(fov / 2); + + camera.position.set(0, 0, dist); + camera.lookAt(0, 0, 0); + camera.updateProjectionMatrix(); + + if (controls) { + const orbit = controls as unknown as OrbitControlsImpl; + orbit.target.set(0, 0, 0); + orbit.update(); + } + + fitted.current = true; + onReady(); + }); + + return null; +}; + const Model = ({ fileUrl, onLoad, @@ -233,21 +275,37 @@ export const ThreeView: React.FC = ({ onError, controlRef, }) => { + const [isReady, setIsReady] = React.useState(false); + const groupRef = useRef(null!); + + const handleReady = useCallback(() => { + setIsReady(true); + onLoad?.(); + }, [onLoad]); + return (
+ {!isReady && ( +
+ )} onError?.(e.message)} > {controlRef && } + @@ -265,23 +323,18 @@ export const ThreeView: React.FC = ({ - - - +
+ + + +