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 { 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 (
<ZoomPanContext.Provider value={{alignView}}>
<div className="zoom-pan-wrapper" ref={wrapperRef}>
<svg
ref={svgRef}
viewBox={getViewBoxString()}
onWheel={onWheel}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
onMouseLeave={onMouseUp}
>
<g ref={cameraWrapperRef} transform={getCameraTransformString()}>
{children}
</g>
</svg>
</div>
</ZoomPanContext.Provider>
<div className="zoom-pan-wrapper" ref={wrapperRef}>
<svg
ref={svgRef}
viewBox={getViewBoxString()}
onWheel={onWheel}
onMouseDown={onMouseDown}
onMouseMove={onMouseMove}
onMouseUp={onMouseUp}
onMouseLeave={onMouseUp}
>
<g ref={cameraWrapperRef} transform={getCameraTransformString()}>
{children}
</g>
</svg>
</div>
);
}

View File

@ -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 (

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';
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);
},
};
}