Added track hover info
+Added a popup showing hovered track's info
This commit is contained in:
parent
715a8ffc7c
commit
66eae33e80
@ -2,6 +2,7 @@ import ZoomPanWrapper from './ZoomPanWrapper';
|
|||||||
import './Map.css';
|
import './Map.css';
|
||||||
import SceneryRoot from './SceneryRoot';
|
import SceneryRoot from './SceneryRoot';
|
||||||
import DistanceMeter from './distance-meter/DistanceMeter';
|
import DistanceMeter from './distance-meter/DistanceMeter';
|
||||||
|
import TrackHoverInfo from './track-hover-info/TrackHoverInfo';
|
||||||
|
|
||||||
export default function Map(props) {
|
export default function Map(props) {
|
||||||
return (
|
return (
|
||||||
@ -10,6 +11,7 @@ export default function Map(props) {
|
|||||||
<SceneryRoot />
|
<SceneryRoot />
|
||||||
<DistanceMeter />
|
<DistanceMeter />
|
||||||
</ZoomPanWrapper>
|
</ZoomPanWrapper>
|
||||||
|
<TrackHoverInfo />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -19,7 +19,8 @@ const ObjectRendererQueue = [
|
|||||||
'additionalComponents': [
|
'additionalComponents': [
|
||||||
ElectrificationStatusPopup
|
ElectrificationStatusPopup
|
||||||
],
|
],
|
||||||
'cond': (layers) => layers['tracks']
|
'cond': (layers) => layers['tracks'],
|
||||||
|
'pointerEvents': true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'name': 'isolations-ids',
|
'name': 'isolations-ids',
|
||||||
|
|||||||
@ -3,6 +3,7 @@ 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 MiscHelper from "../../../helpers/miscHelper";
|
import MiscHelper from "../../../helpers/miscHelper";
|
||||||
|
import { setHoveredTrack, unsetHoveredTrack } from "../../../services/trackHoverInfoService";
|
||||||
|
|
||||||
export default function TrackRenderer(props) {
|
export default function TrackRenderer(props) {
|
||||||
const { object } = props;
|
const { object } = props;
|
||||||
@ -21,6 +22,14 @@ const MemoizedTrackRenderer = React.memo(StatelessTrackRenderer);
|
|||||||
function StatelessTrackRenderer(props) {
|
function StatelessTrackRenderer(props) {
|
||||||
const { object, trackColorMode } = props;
|
const { object, trackColorMode } = props;
|
||||||
|
|
||||||
|
const onMouseEnter = () => {
|
||||||
|
setHoveredTrack(object);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseLeave = () => {
|
||||||
|
unsetHoveredTrack(null);
|
||||||
|
};
|
||||||
|
|
||||||
const path = getTrackPath(object);
|
const path = getTrackPath(object);
|
||||||
const color = getTrackColor(object, trackColorMode);
|
const color = getTrackColor(object, trackColorMode);
|
||||||
const defs = getTrackDefs(object, trackColorMode);
|
const defs = getTrackDefs(object, trackColorMode);
|
||||||
@ -37,6 +46,8 @@ function StatelessTrackRenderer(props) {
|
|||||||
d={path}
|
d={path}
|
||||||
stroke={color}
|
stroke={color}
|
||||||
className="track"
|
className="track"
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
);
|
);
|
||||||
|
|||||||
19
src/components/map/track-hover-info/TrackHoverInfo.css
Normal file
19
src/components/map/track-hover-info/TrackHoverInfo.css
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
.track-hover-info {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 10000;
|
||||||
|
margin: 0.5em;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-hover-info-popup {
|
||||||
|
background-color: #222;
|
||||||
|
border: 2px solid #444;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-hover-info-popup th {
|
||||||
|
text-align: start;
|
||||||
|
padding-right: 2em;
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
46
src/components/map/track-hover-info/TrackHoverInfo.js
Normal file
46
src/components/map/track-hover-info/TrackHoverInfo.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { useState, useEffect, useCallback, useRef, useContext } from "react";
|
||||||
|
import { hoveredTrack$ } from "../../../services/trackHoverInfoService";
|
||||||
|
import TrackHoverInfoPopup from "./TrackHoverInfoPopup";
|
||||||
|
import SettingsContext from "../../../contexts/SettingsContext";
|
||||||
|
import './TrackHoverInfo.css';
|
||||||
|
|
||||||
|
export default function TrackHoverInfo() {
|
||||||
|
const [ hoveredTrack, setHoveredTrack ] = useState(null);
|
||||||
|
const mousePos = useRef({ x: 0, y: 0 });
|
||||||
|
const divRef = useRef(null);
|
||||||
|
|
||||||
|
const handleMouseMove = useCallback((event) => {
|
||||||
|
mousePos.current.x = event.clientX;
|
||||||
|
mousePos.current.y = event.clientY;
|
||||||
|
|
||||||
|
if (!divRef.current) return;
|
||||||
|
divRef.current.style.left = `${mousePos.current.x}px`;
|
||||||
|
divRef.current.style.top = `${mousePos.current.y}px`;
|
||||||
|
}, [mousePos, divRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const subscription = hoveredTrack$.subscribe(date => {
|
||||||
|
setHoveredTrack(date);
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
}
|
||||||
|
}, [handleMouseMove]);
|
||||||
|
|
||||||
|
if (!hoveredTrack) return null;
|
||||||
|
|
||||||
|
const divStyle = {
|
||||||
|
left: `${mousePos.current.x}px`,
|
||||||
|
top: `${mousePos.current.y}px`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="track-hover-info" ref={divRef} style={divStyle}>
|
||||||
|
<TrackHoverInfoPopup track={hoveredTrack} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
src/components/map/track-hover-info/TrackHoverInfoConsts.js
Normal file
17
src/components/map/track-hover-info/TrackHoverInfoConsts.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { ElectrificationStatus } from "../../../model/electrification-status";
|
||||||
|
|
||||||
|
const TrackHoverInfoConsts = {
|
||||||
|
electrification: {
|
||||||
|
[ElectrificationStatus.ELECTRIFIED]: 'Electrified',
|
||||||
|
[ElectrificationStatus.NON_ELECTRIFIED]: 'Non-electrified',
|
||||||
|
[ElectrificationStatus.NOT_CHECKED]: 'Unknown',
|
||||||
|
[ElectrificationStatus.CONFLICT]: 'Error / Conflict',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
'StandardTrack': 'Standard track',
|
||||||
|
'PointTrack': 'Switch track',
|
||||||
|
'BezierTrack': 'Bezier track',
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TrackHoverInfoConsts;
|
||||||
53
src/components/map/track-hover-info/TrackHoverInfoPopup.js
Normal file
53
src/components/map/track-hover-info/TrackHoverInfoPopup.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import TrackHoverInfoConsts from "./TrackHoverInfoConsts";
|
||||||
|
|
||||||
|
export default function TrackHoverInfoPopup(props) {
|
||||||
|
const { track } = props;
|
||||||
|
|
||||||
|
const slopeArr = Array.from(new Set([track['start_slope'], track['end_slope']]))
|
||||||
|
const slopeOptions = {join: ' / ', subOptions: {suffix: ' ‰'}};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="track-hover-info-popup">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<InfoPopupItem value={track['id_station']} label="Track ID" />
|
||||||
|
<InfoPopupItem value={track['id_isolation']} label="Isolation ID" />
|
||||||
|
<InfoPopupItem value={track['derailspeed']} label="Derail speed" options={{suffix: ' km/h'}} />
|
||||||
|
<InfoPopupItem value={track['maxspeed']} label="Max. speed" options={{suffix: ' km/h'}} />
|
||||||
|
<InfoPopupItem value={slopeArr} label="Slope" options={slopeOptions} />
|
||||||
|
<InfoPopupItem value={track['electrificationStatus']} label="Electrification" options={{translationConstKey: 'electrification'}} />
|
||||||
|
<InfoPopupItem value={track['type']} label="Type" options={{translationConstKey: 'type'}} />
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function processItemValue(value, options) {
|
||||||
|
const { translationConstKey, join, subOptions, suffix } = options || {};
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value.map(item => processItemValue(item, subOptions)).join(join || ", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (translationConstKey) {
|
||||||
|
return TrackHoverInfoConsts[translationConstKey]?.[value] || value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${value}${suffix || ''}`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function InfoPopupItem({ label, value, options = {} }) {
|
||||||
|
const textValue = processItemValue(value, options);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>{label}</th>
|
||||||
|
<td>{textValue}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -2,6 +2,7 @@ import { useContext } from "react";
|
|||||||
import MainContext from "../../contexts/MainContext";
|
import MainContext from "../../contexts/MainContext";
|
||||||
import { useZoomPanEmitter } from "../../hooks/useZoomPubSub";
|
import { useZoomPanEmitter } from "../../hooks/useZoomPubSub";
|
||||||
import SceneryFilesHelper from "../../helpers/sceneryFilesHelper";
|
import SceneryFilesHelper from "../../helpers/sceneryFilesHelper";
|
||||||
|
import { resetHoveredTracksStack } from "../../services/trackHoverInfoService";
|
||||||
|
|
||||||
export default function CustomFileSelect() {
|
export default function CustomFileSelect() {
|
||||||
const {setScenery, setIsLoading} = useContext(MainContext);
|
const {setScenery, setIsLoading} = useContext(MainContext);
|
||||||
@ -14,6 +15,7 @@ export default function CustomFileSelect() {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const scenery = await SceneryFilesHelper.loadCustom(file, center);
|
const scenery = await SceneryFilesHelper.loadCustom(file, center);
|
||||||
setScenery(scenery);
|
setScenery(scenery);
|
||||||
|
resetHoveredTracksStack();
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useZoomPanEmitter } from '../../hooks/useZoomPubSub';
|
|||||||
import SceneryFilesHelper from '../../helpers/sceneryFilesHelper';
|
import SceneryFilesHelper from '../../helpers/sceneryFilesHelper';
|
||||||
import { setSceneriesListVersionDate } from '../../services/sceneriesListService';
|
import { setSceneriesListVersionDate } from '../../services/sceneriesListService';
|
||||||
import Constants from '../../helpers/constants';
|
import Constants from '../../helpers/constants';
|
||||||
|
import { resetHoveredTracksStack } from '../../services/trackHoverInfoService';
|
||||||
|
|
||||||
export default function SceneriesList(props) {
|
export default function SceneriesList(props) {
|
||||||
const {setScenery, setIsLoading} = useContext(MainContext);
|
const {setScenery, setIsLoading} = useContext(MainContext);
|
||||||
@ -16,6 +17,7 @@ export default function SceneriesList(props) {
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const scenery = await SceneryFilesHelper.load(name, center);
|
const scenery = await SceneryFilesHelper.load(name, center);
|
||||||
setScenery(scenery);
|
setScenery(scenery);
|
||||||
|
resetHoveredTracksStack();
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
19
src/services/trackHoverInfoService.js
Normal file
19
src/services/trackHoverInfoService.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
|
const hoveredTracksStack = [];
|
||||||
|
export const hoveredTrack$ = new BehaviorSubject(null);
|
||||||
|
|
||||||
|
export function setHoveredTrack(track) {
|
||||||
|
hoveredTracksStack.push(track);
|
||||||
|
hoveredTrack$.next(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unsetHoveredTrack(track) {
|
||||||
|
hoveredTracksStack.splice(hoveredTracksStack.indexOf(track), 1);
|
||||||
|
hoveredTrack$.next(hoveredTracksStack.at(-1) || null);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resetHoveredTracksStack() {
|
||||||
|
hoveredTracksStack.length = 0;
|
||||||
|
hoveredTrack$.next(null);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user