Use ZoomPanEmitter/Subscriber instead of a context for aligning the view

This commit is contained in:
dominik-korsa 2025-07-23 00:44:13 +02:00
parent 2e1947a7d4
commit de6d3aa5dc
No known key found for this signature in database
GPG Key ID: 5A24D76EE0A30974
4 changed files with 48 additions and 42 deletions

View File

@ -3,7 +3,6 @@ import './ZoomPanWrapper.css';
import Constants from '../../helpers/constants'; import Constants from '../../helpers/constants';
import { useZoomPanSubscriber, viewBox$, clientRect$, camera$ } from '../../hooks/useZoomPubSub'; import { useZoomPanSubscriber, viewBox$, clientRect$, camera$ } from '../../hooks/useZoomPubSub';
import {mapRotation$} from '../../services/mapRotationService'; import {mapRotation$} from '../../services/mapRotationService';
import ZoomPanContext from "../../contexts/ZoomPanContext";
import AngleHelper from "../../helpers/angleHelper"; import AngleHelper from "../../helpers/angleHelper";
export default function ZoomPanWrapper({children}) { export default function ZoomPanWrapper({children}) {
@ -91,8 +90,19 @@ export default function ZoomPanWrapper({children}) {
scheduleCameraUpdate(); scheduleCameraUpdate();
}, [scheduleCameraUpdate]); }, [scheduleCameraUpdate]);
// Subscribe to any external "center" calls const alignView = (angleDeg) => {
useZoomPanSubscriber(centerOn); // Find such relative rotation in the range [-45, 45] degrees that aligns the track
// with y rotation angleDeg to the X or Y screen axis.
const angleDifference = -angleDeg - cameraRef.current.rotation;
let deltaAngle = AngleHelper.normalizeDegAngle(angleDifference, 90);
if (deltaAngle > 45) deltaAngle -= 90;
cameraRef.current.rotation += deltaAngle;
scheduleCameraUpdate();
};
// Subscribe to any external `center` and `alignView` calls
useZoomPanSubscriber(centerOn, alignView);
const onWheel = (e) => { const onWheel = (e) => {
const { left, top } = clientRectRef.current; const { left, top } = clientRectRef.current;
@ -149,20 +159,7 @@ export default function ZoomPanWrapper({children}) {
isMouseDownRef.current = false; isMouseDownRef.current = false;
}; };
// `event` could be used in the future to perform the rotation around the click position
const alignView = (angleDeg, _event) => {
// Find such relative rotation in the range [-45, 45] degrees that aligns the track
// with y rotation angleDeg to the X or Y screen axis.
const angleDifference = -angleDeg - cameraRef.current.rotation;
let deltaAngle = AngleHelper.normalizeDegAngle(angleDifference, 90);
if (deltaAngle > 45) deltaAngle -= 90;
cameraRef.current.rotation += deltaAngle;
scheduleCameraUpdate();
};
return ( return (
<ZoomPanContext.Provider value={{alignView}}>
<div className="zoom-pan-wrapper" ref={wrapperRef}> <div className="zoom-pan-wrapper" ref={wrapperRef}>
<svg <svg
ref={svgRef} ref={svgRef}
@ -178,6 +175,5 @@ export default function ZoomPanWrapper({children}) {
</g> </g>
</svg> </svg>
</div> </div>
</ZoomPanContext.Provider>
); );
} }

View File

@ -3,18 +3,18 @@ import SettingsContext from "../../../contexts/SettingsContext";
import Constants from "../../../helpers/constants"; import Constants from "../../../helpers/constants";
import {ElectrificationStatus} from "../../../model/electrification-status"; import {ElectrificationStatus} from "../../../model/electrification-status";
import {setHoveredTrack, unsetHoveredTrack} from "../../../services/trackHoverInfoService"; import {setHoveredTrack, unsetHoveredTrack} from "../../../services/trackHoverInfoService";
import ZoomPanContext from "../../../contexts/ZoomPanContext";
import GradientsContext from "../../../contexts/GradientsContext"; import GradientsContext from "../../../contexts/GradientsContext";
import MiscHelper from "../../../helpers/miscHelper"; import MiscHelper from "../../../helpers/miscHelper";
import {useZoomPanEmitter} from "../../../hooks/useZoomPubSub";
export default function TrackRenderer(props) { export default function TrackRenderer(props) {
const {object} = props; const {object} = props;
const {trackColorMode} = useContext(SettingsContext); const {trackColorMode} = useContext(SettingsContext);
const {gradientDefs} = useContext(GradientsContext); const {gradientDefs} = useContext(GradientsContext);
const {alignView} = useContext(ZoomPanContext); const { alignView } = useZoomPanEmitter();
const onAlign = (event) => { const onAlign = () => {
alignView(object.rot.y, event); alignView(object.rot.y);
}; };
return ( return (

View File

@ -1,4 +0,0 @@
import { createContext } from 'react';
const ZoomPanContext = createContext();
export default ZoomPanContext;

View File

@ -2,6 +2,7 @@ import { useEffect } from 'react';
import { Subject, BehaviorSubject } from 'rxjs'; import { Subject, BehaviorSubject } from 'rxjs';
const zoomCenter$ = new Subject(); const zoomCenter$ = new Subject();
const viewAlign$ = new Subject();
// BehaviorSubjects to store current viewBox, clientRect and camera transform // BehaviorSubjects to store current viewBox, clientRect and camera transform
export const viewBox$ = new BehaviorSubject(null); export const viewBox$ = new BehaviorSubject(null);
@ -32,17 +33,27 @@ export function getCurrentCamera() {
/** /**
* useZoomPanSubscriber * useZoomPanSubscriber
* *
* Registers a callback (onCenter) that will be invoked whenever * Registers an `onCenter` callback that will be invoked whenever
* someone calls `center(x, y)` via the emitter. You should pass * someone calls `center(x, y)` via the emitter. You should pass
* a function that takes (x, y) and recenters your viewBox accordingly. * a function that takes (x, y) and recenters your viewBox accordingly.
*
* Similarly, registers an `onAlign` callback that will be invoked
* when someone calls `alignView(angleDeg)`.
*/ */
export function useZoomPanSubscriber(onCenter) { export function useZoomPanSubscriber(onCenter, onAlign) {
useEffect(() => { useEffect(() => {
const sub = zoomCenter$.subscribe(({ x, y }) => { const sub = zoomCenter$.subscribe(({ x, y }) => {
onCenter(x, y); onCenter(x, y);
}); });
return () => sub.unsubscribe(); return () => sub.unsubscribe();
}, [onCenter]); }, [onCenter]);
useEffect(() => {
const sub = viewAlign$.subscribe((angleDeg) => {
onAlign(angleDeg);
});
return () => sub.unsubscribe();
}, [onAlign]);
} }
/** /**
@ -57,5 +68,8 @@ export function useZoomPanEmitter() {
center: (x, y) => { center: (x, y) => {
zoomCenter$.next({ x, y }); zoomCenter$.next({ x, y });
}, },
alignView: (angleDeg) => {
viewAlign$.next(angleDeg);
},
}; };
} }