Implement embedded mode

- Implemented embedded mode hiding unnecessary UI elements and tweaking scenery loading process
This commit is contained in:
izawartka 2026-07-29 19:49:44 +02:00
parent b68d4e76c0
commit 419fcc7c6d
4 changed files with 30 additions and 12 deletions

View File

@ -10,7 +10,7 @@ import {GradientsManager} from "./GradientsManager";
import SideMenuContext from '../contexts/SideMenuContext'; import SideMenuContext from '../contexts/SideMenuContext';
import SceneryManager from './SceneryManager'; import SceneryManager from './SceneryManager';
import ThemeManager from './ThemeManager'; import ThemeManager from './ThemeManager';
import {EmbeddedContextProvider} from '../contexts/EmbeddedContext'; import {EmbeddedContextProvider, useEmbeddedContext} from '../contexts/EmbeddedContext';
import './Themes.css'; import './Themes.css';
import './App.css'; import './App.css';
@ -38,6 +38,8 @@ function App() {
} }
function AppContent() { function AppContent() {
const { isEmbedded } = useEmbeddedContext();
return ( return (
<div className="App"> <div className="App">
<Toolbar /> <Toolbar />
@ -45,7 +47,7 @@ function AppContent() {
<Map /> <Map />
<SideMenu /> <SideMenu />
</div> </div>
<LocList /> { !isEmbedded && <LocList /> }
<DialogManager /> <DialogManager />
</div> </div>
); );

View File

@ -9,6 +9,7 @@ import SceneryLoadedDialog from './scenery-loaded-dialog/SceneryLoadedDialog';
import Constants from '../helpers/constants'; import Constants from '../helpers/constants';
import MiscHelper from '../helpers/miscHelper'; import MiscHelper from '../helpers/miscHelper';
import ElectrificationResolver from '../model/parsing/electrification-resolver'; import ElectrificationResolver from '../model/parsing/electrification-resolver';
import { useEmbeddedContext } from '../contexts/EmbeddedContext';
export default function SceneryManager(props) { export default function SceneryManager(props) {
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -16,6 +17,7 @@ export default function SceneryManager(props) {
const [sceneryListData, setSceneryListData] = useState(null); const [sceneryListData, setSceneryListData] = useState(null);
const urlParamsLoaded = useRef(false); const urlParamsLoaded = useRef(false);
const { setCamera } = useZoomPanEmitter(); const { setCamera } = useZoomPanEmitter();
const { isEmbedded } = useEmbeddedContext();
const myFetch = useCallback(async (url, toJson) => { const myFetch = useCallback(async (url, toJson) => {
const response = await fetch(url); const response = await fetch(url);
@ -39,14 +41,15 @@ export default function SceneryManager(props) {
}, [myFetch]); }, [myFetch]);
const updateViewAfterSceneryLoaded = useCallback((scenery) => { const updateViewAfterSceneryLoaded = useCallback((scenery) => {
const targetZoom = isEmbedded ? Constants.map.zoomDefaultEmbedded : Constants.map.zoomDefault;
const mainSignalBox = scenery.getMainSignalBox(); const mainSignalBox = scenery.getMainSignalBox();
if(mainSignalBox) { if(mainSignalBox) {
const sbPos = mainSignalBox.pos.toSVGCoords(); const sbPos = mainSignalBox.pos.toSVGCoords();
const sbRot = -mainSignalBox.getFinalRotation(); const sbRot = -mainSignalBox.getFinalRotation();
setCamera(...sbPos, sbRot, Constants.map.zoomDefault); setCamera(...sbPos, sbRot, targetZoom);
} }
else setCamera(0, 0, 0, Constants.map.zoomDefault); else setCamera(0, 0, 0, targetZoom);
}, [setCamera]); }, [setCamera, isEmbedded]);
const loadCustomScenery = useCallback((file) => { const loadCustomScenery = useCallback((file) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -64,10 +67,12 @@ export default function SceneryManager(props) {
setScenery(null); setScenery(null);
SceneryParserLog.clear(); SceneryParserLog.clear();
const urlParams = new URLSearchParams(window.location.search); if (!isEmbedded) {
urlParams.delete('scenery'); const urlParams = new URLSearchParams(window.location.search);
urlParams.delete('pos'); urlParams.delete('scenery');
window.history.replaceState({}, '', urlParams.size ? `${window.location.pathname}?${urlParams.toString()}` : window.location.pathname); urlParams.delete('pos');
window.history.replaceState({}, '', urlParams.size ? `${window.location.pathname}?${urlParams.toString()}` : window.location.pathname);
}
const reader = new FileReader(); const reader = new FileReader();
reader.readAsText(file); reader.readAsText(file);
@ -83,13 +88,17 @@ export default function SceneryManager(props) {
console.error(error); console.error(error);
} }
showCustomDialog(<SceneryLoadedDialog loadingError={loadingError} />); if (isEmbedded && loadingError) {
showDialog('Failed to load scenery');
} else if (!isEmbedded) {
showCustomDialog(<SceneryLoadedDialog loadingError={loadingError} />);
}
setScenery(scenery); setScenery(scenery);
setIsLoading(false); setIsLoading(false);
resolve(scenery); resolve(scenery);
} }
}); });
}, [updateViewAfterSceneryLoaded]); }, [updateViewAfterSceneryLoaded, isEmbedded]);
const loadScenery = useCallback(async (sceneryName) => { const loadScenery = useCallback(async (sceneryName) => {
let file; let file;
@ -130,6 +139,7 @@ export default function SceneryManager(props) {
const sceneryName = findSceneryInList(sceneryQuery); const sceneryName = findSceneryInList(sceneryQuery);
if (!sceneryName) { if (!sceneryName) {
console.warn(`Scenery "${sceneryQuery}" not found in the list.`); console.warn(`Scenery "${sceneryQuery}" not found in the list.`);
showDialog(`Scenery "${sceneryQuery}" not found in the list.`);
return; return;
} }
@ -140,6 +150,7 @@ export default function SceneryManager(props) {
if(!posQuery) return; if(!posQuery) return;
if (!pos) { if (!pos) {
console.warn(`Invalid position query "${posQuery}"`); console.warn(`Invalid position query "${posQuery}"`);
showDialog("Invalid position query in URL.");
return; return;
} }

View File

@ -5,11 +5,14 @@ import SideMenuToggle from './SideMenuToggle';
import SceneryList from './SceneryList'; import SceneryList from './SceneryList';
import ElectrificationStepMode from './ElectrificationStepMode'; import ElectrificationStepMode from './ElectrificationStepMode';
import ThemeToggle from './ThemeToggle'; import ThemeToggle from './ThemeToggle';
import { useEmbeddedContext } from '../../contexts/EmbeddedContext';
export default function Toolbar() { export default function Toolbar() {
const { isEmbedded } = useEmbeddedContext();
return ( return (
<div className='toolbar-cont'> <div className='toolbar-cont'>
<div className='toolbar'> {!isEmbedded && <div className='toolbar'>
<SceneryList /> <SceneryList />
or or
<CustomFileSelect /> <CustomFileSelect />
@ -18,6 +21,7 @@ export default function Toolbar() {
<ThemeToggle /> <ThemeToggle />
<SideMenuToggle /> <SideMenuToggle />
</div> </div>
}
<div className='loading-indicator-cont'> <div className='loading-indicator-cont'>
<LoadingIndicator /> <LoadingIndicator />
</div> </div>

View File

@ -5,6 +5,7 @@ const Constants = {
zoomMin: 0.03, zoomMin: 0.03,
zoomMax: 200.0, zoomMax: 200.0,
zoomDefault: 5, zoomDefault: 5,
zoomDefaultEmbeded: 2.5,
rotationSensitivity: 0.2, rotationSensitivity: 0.2,
forcePointerEvents: false, forcePointerEvents: false,
platformMaxTilt: 0.5, platformMaxTilt: 0.5,