diff --git a/src/components/loclist/ClickableLocation.js b/src/components/loclist/ClickableLocation.js index 12f5261..866c75a 100644 --- a/src/components/loclist/ClickableLocation.js +++ b/src/components/loclist/ClickableLocation.js @@ -1,11 +1,11 @@ import { useZoomPanEmitter } from '../../hooks/useZoomPubSub'; export default function ClickableLocation(props) { - const { name, pos } = props; - const { center } = useZoomPanEmitter(); + const { name, pos, rot } = props; + const { setCamera } = useZoomPanEmitter(); const handleClick = () => { - center(pos[0], pos[1]); + setCamera(pos[0], pos[1], rot); }; return ( diff --git a/src/components/loclist/LocList.js b/src/components/loclist/LocList.js index 62e5fc4..cfada3c 100644 --- a/src/components/loclist/LocList.js +++ b/src/components/loclist/LocList.js @@ -10,9 +10,10 @@ export default function LocList(props) { if (!scenery?.signalBoxes) return null; return scenery.signalBoxes.map(signalBox => { const pos = signalBox.pos.toSVGCoords(); + const rot = -signalBox.getFinalRotation(); const name = signalBox.getPrintableSignalBoxName(); return ( - + ); }); }, [scenery?.signalBoxes]); diff --git a/src/components/map/ZoomPanWrapper.js b/src/components/map/ZoomPanWrapper.js index 2a5295e..ec1f861 100644 --- a/src/components/map/ZoomPanWrapper.js +++ b/src/components/map/ZoomPanWrapper.js @@ -5,6 +5,10 @@ import { useZoomPanSubscriber, viewBox$, clientRect$, camera$ } from '../../hook import {mapRotation$} from '../../services/mapRotationService'; import AngleHelper from "../../helpers/angleHelper"; +function boundZoom(zoom) { + return Math.max(Constants.map.zoomMin, Math.min(Constants.map.zoomMax, zoom)); +} + export default function ZoomPanWrapper({children}) { const wrapperRef = useRef(null); const svgRef = useRef(null); @@ -83,9 +87,11 @@ export default function ZoomPanWrapper({children}) { }; }, [handleResize]); - const centerOn = useCallback((cx, cy) => { + const setCamera = useCallback((cx, cy, rot, zoom) => { cameraRef.current.x = cx; cameraRef.current.y = cy; + if (rot || rot === 0) cameraRef.current.rotation = AngleHelper.normalizeDegAngle(rot); + if (zoom) cameraRef.current.zoom = boundZoom(zoom); scheduleCameraUpdate(); }, [scheduleCameraUpdate]); @@ -101,8 +107,8 @@ export default function ZoomPanWrapper({children}) { scheduleCameraUpdate(); }; - // Subscribe to any external `center` and `alignView` calls - useZoomPanSubscriber(centerOn, alignView); + // Subscribe to any external `setCamera` and `alignView` calls + useZoomPanSubscriber(setCamera, alignView); const onWheel = (e) => { const { left, top } = clientRectRef.current; @@ -110,7 +116,7 @@ export default function ZoomPanWrapper({children}) { const { w, h } = viewBoxRef.current; const newZoomCalc = cameraRef.current.zoom * (1.0 - (e.deltaY * Constants.map.zoomSensitivity)); - const newZoom = Math.max(Constants.map.zoomMin, Math.min(Constants.map.zoomMax, newZoomCalc)); + const newZoom = boundZoom(newZoomCalc); cameraRef.current.zoom = newZoom; // screen space cursor pos diff --git a/src/components/toolbar/CustomFileSelect.js b/src/components/toolbar/CustomFileSelect.js index d1fbc90..59678e8 100644 --- a/src/components/toolbar/CustomFileSelect.js +++ b/src/components/toolbar/CustomFileSelect.js @@ -6,14 +6,14 @@ import { resetHoveredTracksStack } from "../../services/trackHoverInfoService"; export default function CustomFileSelect() { const {setScenery, setIsLoading} = useContext(MainContext); - const { center } = useZoomPanEmitter(); + const { setCamera } = useZoomPanEmitter(); const handleFileChange = async (event) => { const file = event.target.files[0]; if (!file) return; setIsLoading(true); - const scenery = await SceneryFilesHelper.loadCustom(file, center); + const scenery = await SceneryFilesHelper.loadCustom(file, setCamera); setScenery(scenery); resetHoveredTracksStack(); setIsLoading(false); diff --git a/src/components/toolbar/SceneriesList.js b/src/components/toolbar/SceneriesList.js index 61fd586..6f0202e 100644 --- a/src/components/toolbar/SceneriesList.js +++ b/src/components/toolbar/SceneriesList.js @@ -9,13 +9,13 @@ import { resetHoveredTracksStack } from '../../services/trackHoverInfoService'; export default function SceneriesList(props) { const {setScenery, setIsLoading} = useContext(MainContext); const [ sceneriesListData, setSceneriesListData ] = useState(null); - const { center } = useZoomPanEmitter(); + const { setCamera } = useZoomPanEmitter(); const handleSelectChange = async (event) => { const name = event.target.value; setIsLoading(true); - const scenery = await SceneryFilesHelper.load(name, center); + const scenery = await SceneryFilesHelper.load(name, setCamera); setScenery(scenery); resetHoveredTracksStack(); setIsLoading(false); diff --git a/src/helpers/constants.js b/src/helpers/constants.js index 64382d3..f6f044f 100644 --- a/src/helpers/constants.js +++ b/src/helpers/constants.js @@ -4,6 +4,7 @@ const Constants = { zoomSensitivity: 0.002, zoomMin: 0.03, zoomMax: 200.0, + zoomDefault: 5, rotationSensitivity: 0.2, forcePointerEvents: false, platformMaxTilt: 0.5 diff --git a/src/helpers/sceneryFilesHelper.js b/src/helpers/sceneryFilesHelper.js index 928f7b3..5c1e004 100644 --- a/src/helpers/sceneryFilesHelper.js +++ b/src/helpers/sceneryFilesHelper.js @@ -30,7 +30,7 @@ export default class SceneryFilesHelper { return await this.fetch(url, false); } - static async load(name, centerFn = null) { + static async load(name, setCameraFn = null) { let file; try { file = await this.getScenery(name); @@ -43,10 +43,10 @@ export default class SceneryFilesHelper { return null; } - return await this.loadCustom(file, centerFn); + return await this.loadCustom(file, setCameraFn); } - static async loadCustom(file, centerFn = null) { + static async loadCustom(file, setCameraFn = null) { return new Promise((resolve, reject) => { SceneryParserLog.clear(); const reader = new FileReader(); @@ -56,8 +56,13 @@ export default class SceneryFilesHelper { let loadingError = null; try { scenery = SceneryParser.fromText(e.target?.result); - const mainSignalBoxPos = scenery.signalBoxes?.[0]?.pos || null; - if (mainSignalBoxPos) centerFn(...mainSignalBoxPos.toSVGCoords()); + const mainSignalBox = scenery.signalBoxes?.[0]; + if(mainSignalBox) { + const sbPos = mainSignalBox.pos.toSVGCoords(); + const sbRot = -mainSignalBox.getFinalRotation(); + setCameraFn(...sbPos, sbRot, Constants.map.zoomDefault); + } + else setCameraFn(0, 0, 0, Constants.map.zoomDefault); } catch (error) { scenery = null; loadingError = error; diff --git a/src/hooks/useZoomPubSub.js b/src/hooks/useZoomPubSub.js index f948b73..d4115c5 100644 --- a/src/hooks/useZoomPubSub.js +++ b/src/hooks/useZoomPubSub.js @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import { Subject, BehaviorSubject } from 'rxjs'; -const zoomCenter$ = new Subject(); +const setCamera$ = new Subject(); const viewAlign$ = new Subject(); // BehaviorSubjects to store current viewBox, clientRect and camera transform @@ -21,13 +21,13 @@ export function getCurrentCamera() { return camera$.getValue(); } -export function useZoomPanSubscriber(onCenter, onAlign) { +export function useZoomPanSubscriber(setCamera, onAlign) { useEffect(() => { - const sub = zoomCenter$.subscribe(({ x, y }) => { - onCenter(x, y); + const sub = setCamera$.subscribe(({ x, y, rot, zoom }) => { + setCamera(x, y, rot, zoom); }); return () => sub.unsubscribe(); - }, [onCenter]); + }, [setCamera]); useEffect(() => { const sub = viewAlign$.subscribe((angleDeg) => { @@ -39,8 +39,8 @@ export function useZoomPanSubscriber(onCenter, onAlign) { export function useZoomPanEmitter() { return { - center: (x, y) => { - zoomCenter$.next({ x, y }); + setCamera: (x, y, rot = null, zoom = null) => { + setCamera$.next({ x, y, rot, zoom }); }, alignView: (angleDeg) => { viewAlign$.next(angleDeg);