Files
blockly3d/webclient/src/App.tsx
T
2026-06-21 15:38:41 +03:00

120 lines
3.8 KiB
TypeScript

import { BrowserRouter, Link, Outlet, Route, Routes, useLocation, useNavigate, useParams } from 'react-router-dom';
import { VoxelEditorPage } from './components/voxelEditor/VoxelEditorPage';
import { useEffect, useLayoutEffect, useRef } from 'react';
import { reaction } from 'mobx';
import { state } from './state';
import './App.scss';
import { Panels } from './components/Panels';
import { LayeredThreeView } from './components/layers/LayeredThreeView';
function StateToUrlSync() {
const navigate = useNavigate();
const { pathname } = useLocation();
const pathnameRef = useRef(pathname);
useLayoutEffect(() => { pathnameRef.current = pathname; });
useEffect(() => reaction(
() => ({
isGame: !!state.game,
selectedObjectId: state.worldEditor.selection?.type === 'object' && state.worldEditor.selection.id,
selectedObjectTypeId: state.worldEditor.selection?.type === 'objectType' && state.worldEditor.selection.id,
}),
({ isGame, selectedObjectId, selectedObjectTypeId }) => {
let target: string;
if (isGame)
target = '/game';
else if (selectedObjectId)
target = `/editor/object/${selectedObjectId}`;
else if (selectedObjectTypeId)
target = `/editor/objectType/${selectedObjectTypeId}`;
else
target = '/editor';
const current = pathnameRef.current;
if (current === target || current.startsWith(target + '/'))
return;
navigate(target);
},
), [navigate]);
return null;
}
function EditorRoute() {
useEffect(() => {
if (state.game)
state.stopGame();
state.worldEditor.resetSelection();
}, []);
return null;
}
function EditorObjectRoute() {
const { id } = useParams<{ id: string }>();
useEffect(() => {
if (!id)
return;
if (state.game)
state.stopGame();
const editMode = state.worldEditor.selection?.type === 'object' && state.worldEditor.selection.id === id
? state.worldEditor.selection.editMode ?? 'translate'
: 'translate';
state.worldEditor.setSelectedObject({ id, editMode });
}, [id]);
return null;
}
function EditorObjectTypeRoute() {
const { id } = useParams<{ id: string }>();
useEffect(() => {
if (!id)
return;
if (state.game)
state.stopGame();
const editMode = state.worldEditor.selection?.type === 'objectType' && state.worldEditor.selection.id === id
? state.worldEditor.selection.editMode ?? 'scripts'
: 'scripts';
state.worldEditor.setSelectedObjectType({ id, editMode });
}, [id]);
return null;
}
function GameRoute() {
useEffect(() => {
if (!state.game)
state.startGame();
}, []);
return null;
}
function HomePage() {
return <Link to="/editor">Open Editor</Link>;
}
function EditorLayout() {
return (
<div className="editor-layout">
<Outlet />
<LayeredThreeView />
<Panels />
</div>
);
}
export const App = function () {
return (
<BrowserRouter>
<StateToUrlSync />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/editor/objectType/:id/voxels" element={<VoxelEditorPage />} />
<Route element={<EditorLayout />}>
<Route path="/editor" element={<EditorRoute />} />
<Route path="/editor/object/:id" element={<EditorObjectRoute />} />
<Route path="/editor/objectType/:id" element={<EditorObjectTypeRoute />} />
<Route path="/game" element={<GameRoute />} />
</Route>
</Routes>
</BrowserRouter>
);
};