fix: stabilize right widget layout and sight frame menu height with lang
This commit is contained in:
@@ -1,13 +1,19 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { Router } from "./router";
|
import { Router } from "./router";
|
||||||
import { CustomTheme } from "@shared";
|
import { CustomTheme, languageStore } from "@shared";
|
||||||
import { ThemeProvider } from "@mui/material/styles";
|
import { ThemeProvider } from "@mui/material/styles";
|
||||||
import { ToastContainer } from "react-toastify";
|
import { ToastContainer } from "react-toastify";
|
||||||
import { GlobalErrorBoundary } from "./GlobalErrorBoundary";
|
import { GlobalErrorBoundary } from "./GlobalErrorBoundary";
|
||||||
import { TestingModeBanner } from "@widgets";
|
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 (
|
||||||
<GlobalErrorBoundary>
|
<GlobalErrorBoundary>
|
||||||
<ThemeProvider theme={CustomTheme.Light}>
|
<ThemeProvider theme={CustomTheme.Light}>
|
||||||
<TestingModeBanner />
|
<TestingModeBanner />
|
||||||
@@ -16,3 +22,4 @@ export const App: React.FC = () => (
|
|||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</GlobalErrorBoundary>
|
</GlobalErrorBoundary>
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
|||||||
@@ -411,7 +411,7 @@ const ListOfSights = observer(() => {
|
|||||||
}, [currentSelectedSight]);
|
}, [currentSelectedSight]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="right-widget">
|
<div className="right-widget" lang={selectedLanguageRight}>
|
||||||
{currentSelectedSight && (
|
{currentSelectedSight && (
|
||||||
<SightFrame
|
<SightFrame
|
||||||
key={currentSelectedSight.id}
|
key={currentSelectedSight.id}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ const LeftWidget = observer(
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={widgetRef} style={widgetTransformStyle} className="left-widget">
|
<div ref={widgetRef} style={widgetTransformStyle} className="left-widget" lang={selectedLanguage}>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div>Загрузка информации...</div>
|
<div>Загрузка информации...</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Canvas, useThree } from "@react-three/fiber";
|
import { Canvas, useThree, useFrame } from "@react-three/fiber";
|
||||||
import { OrbitControls, Stage, useGLTF } from "@react-three/drei";
|
import { OrbitControls, Center, useGLTF } from "@react-three/drei";
|
||||||
import React, { useEffect, Suspense } from "react";
|
import React, { useEffect, useRef, Suspense, useCallback } from "react";
|
||||||
import { BACKGROUND_COLOR } from "../../assets/Constants";
|
import { BACKGROUND_COLOR } from "../../assets/Constants";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import type { OrbitControls as OrbitControlsImpl } from "three-stdlib";
|
import type { OrbitControls as OrbitControlsImpl } from "three-stdlib";
|
||||||
@@ -23,6 +23,7 @@ interface ThreeViewProps {
|
|||||||
const ZOOM_FACTOR = 1.2;
|
const ZOOM_FACTOR = 1.2;
|
||||||
const MIN_DISTANCE = 1;
|
const MIN_DISTANCE = 1;
|
||||||
const MAX_DISTANCE = 100;
|
const MAX_DISTANCE = 100;
|
||||||
|
const CAMERA_FOV = 40;
|
||||||
|
|
||||||
const TouchController = () => {
|
const TouchController = () => {
|
||||||
const { camera, controls, gl } = useThree();
|
const { camera, controls, gl } = useThree();
|
||||||
@@ -197,6 +198,47 @@ const AutoResize = () => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FitCamera = ({
|
||||||
|
groupRef,
|
||||||
|
onReady,
|
||||||
|
}: {
|
||||||
|
groupRef: React.RefObject<THREE.Group>;
|
||||||
|
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 = ({
|
const Model = ({
|
||||||
fileUrl,
|
fileUrl,
|
||||||
onLoad,
|
onLoad,
|
||||||
@@ -233,21 +275,37 @@ export const ThreeView: React.FC<ThreeViewProps> = ({
|
|||||||
onError,
|
onError,
|
||||||
controlRef,
|
controlRef,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [isReady, setIsReady] = React.useState(false);
|
||||||
|
const groupRef = useRef<THREE.Group>(null!);
|
||||||
|
|
||||||
|
const handleReady = useCallback(() => {
|
||||||
|
setIsReady(true);
|
||||||
|
onLoad?.();
|
||||||
|
}, [onLoad]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width, height, position: "relative", overflow: "hidden" }}>
|
<div style={{ width, height, position: "relative", overflow: "hidden" }}>
|
||||||
|
{!isReady && (
|
||||||
|
<div style={{
|
||||||
|
position: "absolute", inset: 0,
|
||||||
|
backgroundColor: BACKGROUND_COLOR,
|
||||||
|
zIndex: 1,
|
||||||
|
}} />
|
||||||
|
)}
|
||||||
<Canvas
|
<Canvas
|
||||||
gl={{
|
gl={{
|
||||||
antialias: true,
|
antialias: true,
|
||||||
toneMappingExposure: 1.5,
|
toneMappingExposure: 1.5,
|
||||||
outputColorSpace: THREE.SRGBColorSpace,
|
outputColorSpace: THREE.SRGBColorSpace,
|
||||||
}}
|
}}
|
||||||
camera={{ position: [0, 0, 5], fov: 40 }}
|
camera={{ position: [0, 0, 50], fov: CAMERA_FOV }}
|
||||||
style={{ width: "100%", height: "100%" }}
|
style={{ width: "100%", height: "100%" }}
|
||||||
onError={(e: any) => onError?.(e.message)}
|
onError={(e: any) => onError?.(e.message)}
|
||||||
>
|
>
|
||||||
<AutoResize />
|
<AutoResize />
|
||||||
<TouchController />
|
<TouchController />
|
||||||
{controlRef && <ZoomController controlRef={controlRef} />}
|
{controlRef && <ZoomController controlRef={controlRef} />}
|
||||||
|
<FitCamera groupRef={groupRef} onReady={handleReady} />
|
||||||
<color attach="background" args={[BACKGROUND_COLOR]} />
|
<color attach="background" args={[BACKGROUND_COLOR]} />
|
||||||
<ambientLight intensity={0.8} />
|
<ambientLight intensity={0.8} />
|
||||||
<directionalLight position={[30, 30, 30]} intensity={1.2} />
|
<directionalLight position={[30, 30, 30]} intensity={1.2} />
|
||||||
@@ -265,23 +323,18 @@ export const ThreeView: React.FC<ThreeViewProps> = ({
|
|||||||
<pointLight position={[0, 30, 0]} intensity={0.6} />
|
<pointLight position={[0, 30, 0]} intensity={0.6} />
|
||||||
|
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Stage
|
<Center precise>
|
||||||
environment={null}
|
<group ref={groupRef}>
|
||||||
intensity={1}
|
<Model fileUrl={fileUrl} />
|
||||||
castShadow={false}
|
</group>
|
||||||
shadows={false}
|
</Center>
|
||||||
adjustCamera={true}
|
|
||||||
center={{ precise: true }}
|
|
||||||
>
|
|
||||||
<Model fileUrl={fileUrl} onLoad={onLoad} />
|
|
||||||
</Stage>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<OrbitControls
|
<OrbitControls
|
||||||
makeDefault
|
makeDefault
|
||||||
enableZoom={true}
|
enableZoom={true}
|
||||||
enablePan={true}
|
enablePan={true}
|
||||||
target={[50, 50, 50]}
|
target={[0, 0, 0]}
|
||||||
minDistance={1}
|
minDistance={1}
|
||||||
maxDistance={100}
|
maxDistance={100}
|
||||||
enableDamping={true}
|
enableDamping={true}
|
||||||
|
|||||||
@@ -433,6 +433,7 @@
|
|||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
transition:
|
transition:
|
||||||
background-color 0.1s ease,
|
background-color 0.1s ease,
|
||||||
color 0.1s ease;
|
color 0.1s ease;
|
||||||
@@ -440,7 +441,7 @@
|
|||||||
|
|
||||||
.sight-frame-menu-point.active {
|
.sight-frame-menu-point.active {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-bottom: 2px solid #fff;
|
border-bottom-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sight-frame-text-wrapper::-webkit-scrollbar-track {
|
.sight-frame-text-wrapper::-webkit-scrollbar-track {
|
||||||
@@ -594,7 +595,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alphabet {
|
.alphabet {
|
||||||
width: 100px;
|
width: 40px;
|
||||||
|
flex-shrink: 0;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
padding-top: 24px;
|
padding-top: 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -654,8 +656,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alphabet-position {
|
.alphabet-position {
|
||||||
display: inline-flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transfer-button-container {
|
.transfer-button-container {
|
||||||
|
|||||||
Reference in New Issue
Block a user