From de6d3aa5dc0ca81484cd21e54f9da516298deb1a Mon Sep 17 00:00:00 2001 From: dominik-korsa <29484605+dominik-korsa@users.noreply.github.com> Date: Wed, 23 Jul 2025 00:44:13 +0200 Subject: [PATCH] Use ZoomPanEmitter/Subscriber instead of a context for aligning the view --- src/components/map/ZoomPanWrapper.js | 60 +++++++++---------- .../map/object-renderers/TrackRenderer.js | 8 +-- src/contexts/ZoomPanContext.js | 4 -- src/hooks/useZoomPubSub.js | 18 +++++- 4 files changed, 48 insertions(+), 42 deletions(-) delete mode 100644 src/contexts/ZoomPanContext.js diff --git a/src/components/map/ZoomPanWrapper.js b/src/components/map/ZoomPanWrapper.js index b28b95b..2a5295e 100644 --- a/src/components/map/ZoomPanWrapper.js +++ b/src/components/map/ZoomPanWrapper.js @@ -3,7 +3,6 @@ import './ZoomPanWrapper.css'; import Constants from '../../helpers/constants'; import { useZoomPanSubscriber, viewBox$, clientRect$, camera$ } from '../../hooks/useZoomPubSub'; import {mapRotation$} from '../../services/mapRotationService'; -import ZoomPanContext from "../../contexts/ZoomPanContext"; import AngleHelper from "../../helpers/angleHelper"; export default function ZoomPanWrapper({children}) { @@ -91,8 +90,19 @@ export default function ZoomPanWrapper({children}) { scheduleCameraUpdate(); }, [scheduleCameraUpdate]); - // Subscribe to any external "center" calls - useZoomPanSubscriber(centerOn); + const alignView = (angleDeg) => { + // 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 { left, top } = clientRectRef.current; @@ -149,35 +159,21 @@ export default function ZoomPanWrapper({children}) { 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 ( - -
- - - {children} - - -
-
+
+ + + {children} + + +
); } diff --git a/src/components/map/object-renderers/TrackRenderer.js b/src/components/map/object-renderers/TrackRenderer.js index 9c67e4a..6a7496b 100644 --- a/src/components/map/object-renderers/TrackRenderer.js +++ b/src/components/map/object-renderers/TrackRenderer.js @@ -3,18 +3,18 @@ import SettingsContext from "../../../contexts/SettingsContext"; import Constants from "../../../helpers/constants"; import {ElectrificationStatus} from "../../../model/electrification-status"; import {setHoveredTrack, unsetHoveredTrack} from "../../../services/trackHoverInfoService"; -import ZoomPanContext from "../../../contexts/ZoomPanContext"; import GradientsContext from "../../../contexts/GradientsContext"; import MiscHelper from "../../../helpers/miscHelper"; +import {useZoomPanEmitter} from "../../../hooks/useZoomPubSub"; export default function TrackRenderer(props) { const {object} = props; const {trackColorMode} = useContext(SettingsContext); const {gradientDefs} = useContext(GradientsContext); - const {alignView} = useContext(ZoomPanContext); + const { alignView } = useZoomPanEmitter(); - const onAlign = (event) => { - alignView(object.rot.y, event); + const onAlign = () => { + alignView(object.rot.y); }; return ( diff --git a/src/contexts/ZoomPanContext.js b/src/contexts/ZoomPanContext.js deleted file mode 100644 index 98c8cfe..0000000 --- a/src/contexts/ZoomPanContext.js +++ /dev/null @@ -1,4 +0,0 @@ -import { createContext } from 'react'; - -const ZoomPanContext = createContext(); -export default ZoomPanContext; diff --git a/src/hooks/useZoomPubSub.js b/src/hooks/useZoomPubSub.js index fa500be..acaf8c3 100644 --- a/src/hooks/useZoomPubSub.js +++ b/src/hooks/useZoomPubSub.js @@ -2,6 +2,7 @@ import { useEffect } from 'react'; import { Subject, BehaviorSubject } from 'rxjs'; const zoomCenter$ = new Subject(); +const viewAlign$ = new Subject(); // BehaviorSubjects to store current viewBox, clientRect and camera transform export const viewBox$ = new BehaviorSubject(null); @@ -32,17 +33,27 @@ export function getCurrentCamera() { /** * 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 * 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(() => { const sub = zoomCenter$.subscribe(({ x, y }) => { onCenter(x, y); }); return () => sub.unsubscribe(); }, [onCenter]); + + useEffect(() => { + const sub = viewAlign$.subscribe((angleDeg) => { + onAlign(angleDeg); + }); + return () => sub.unsubscribe(); + }, [onAlign]); } /** @@ -57,5 +68,8 @@ export function useZoomPanEmitter() { center: (x, y) => { zoomCenter$.next({ x, y }); }, + alignView: (angleDeg) => { + viewAlign$.next(angleDeg); + }, }; }