Extract TrackHoverInfoTable component
- Extracted TrackHoverInfoTable as a separate component
This commit is contained in:
parent
1d86f0f42f
commit
daa86d7b85
@ -12,16 +12,16 @@
|
|||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-hover-info-popup svg {
|
.track-hover-info-popup-table svg {
|
||||||
width: 1.2em;
|
width: 1.2em;
|
||||||
height: 1.2em;
|
height: 1.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-hover-info-popup table {
|
.track-hover-info-popup-table {
|
||||||
border-spacing: 2px;
|
border-spacing: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-hover-info-popup th {
|
.track-hover-info-popup-table th {
|
||||||
text-align: start;
|
text-align: start;
|
||||||
padding-right: 2em;
|
padding-right: 2em;
|
||||||
color: var(--text-color-disabled);
|
color: var(--text-color-disabled);
|
||||||
@ -33,7 +33,7 @@
|
|||||||
color: var(--text-color-disabled);
|
color: var(--text-color-disabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
.track-hover-info-popup .track-hover-info-popup-label {
|
.track-hover-info-popup-table .track-hover-info-popup-label {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { useState, useEffect, useCallback, useRef, useContext } from "react";
|
import { useState, useEffect, useCallback, useRef, useContext, useMemo } from "react";
|
||||||
import { hoveredTrack$ } from "../../../services/trackHoverInfoService";
|
import { hoveredTrack$, touchClickedTrack$ } from "../../../services/trackHoverInfoService";
|
||||||
import TrackHoverInfoPopup from "./TrackHoverInfoPopup";
|
import TrackHoverInfoPopup from "./TrackHoverInfoPopup";
|
||||||
import SettingsContext from "../../../contexts/SettingsContext";
|
import SettingsContext from "../../../contexts/SettingsContext";
|
||||||
|
import { showDialog } from "../../../services/dialogService";
|
||||||
import './TrackHoverInfo.css';
|
import './TrackHoverInfo.css';
|
||||||
|
import TrackHoverInfoPopupTable from "./TrackHoverInfoTable";
|
||||||
|
|
||||||
export default function TrackHoverInfo() {
|
export default function TrackHoverInfo() {
|
||||||
const { showTrackHoverInfo } = useContext(SettingsContext);
|
const { showTrackHoverInfo } = useContext(SettingsContext);
|
||||||
@ -18,6 +20,7 @@ function InnerTrackHoverInfo() {
|
|||||||
const [ hoveredTrack, setHoveredTrack ] = useState(null);
|
const [ hoveredTrack, setHoveredTrack ] = useState(null);
|
||||||
const mousePos = useRef({ x: 0, y: 0 });
|
const mousePos = useRef({ x: 0, y: 0 });
|
||||||
const divRef = useRef(null);
|
const divRef = useRef(null);
|
||||||
|
const isMobile = useMemo(() => window.matchMedia('(pointer: coarse)').matches, []);
|
||||||
|
|
||||||
const handleMouseMove = useCallback((event) => {
|
const handleMouseMove = useCallback((event) => {
|
||||||
mousePos.current.x = event.clientX;
|
mousePos.current.x = event.clientX;
|
||||||
@ -26,19 +29,34 @@ function InnerTrackHoverInfo() {
|
|||||||
if (!divRef.current) return;
|
if (!divRef.current) return;
|
||||||
divRef.current.style.left = `${mousePos.current.x}px`;
|
divRef.current.style.left = `${mousePos.current.x}px`;
|
||||||
divRef.current.style.top = `${mousePos.current.y}px`;
|
divRef.current.style.top = `${mousePos.current.y}px`;
|
||||||
}, [mousePos, divRef]);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = hoveredTrack$.subscribe(date => {
|
const hoveredTrackSub = hoveredTrack$.subscribe(track => {
|
||||||
setHoveredTrack(date);
|
if (isMobile) return;
|
||||||
|
setHoveredTrack(track);
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('mousemove', handleMouseMove);
|
const touchClickedTrackSub = touchClickedTrack$.subscribe(track => {
|
||||||
|
if (!isMobile || !track) return;
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
<TrackHoverInfoPopupTable track={track} />,
|
||||||
|
[{ text: 'Close', onClick: () => {}, autoFocus: true, bgClick: true }]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isMobile) {
|
||||||
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
subscription.unsubscribe();
|
hoveredTrackSub.unsubscribe();
|
||||||
document.removeEventListener('mousemove', handleMouseMove);
|
touchClickedTrackSub.unsubscribe();
|
||||||
}
|
if (!isMobile) {
|
||||||
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [handleMouseMove]);
|
}, [handleMouseMove]);
|
||||||
|
|
||||||
if (!hoveredTrack) return null;
|
if (!hoveredTrack) return null;
|
||||||
|
|||||||
@ -1,35 +1,12 @@
|
|||||||
import Constants from "../../../helpers/constants";
|
|
||||||
import { QuestionMarkIcon, SlopeIcon, SpeedIcon, HashIcon, LightningIcon, RadiusIcon } from "../../../icons";
|
|
||||||
import { TrackShape } from "../../../model/tracks/track";
|
import { TrackShape } from "../../../model/tracks/track";
|
||||||
import TrackHoverInfoConsts from "./TrackHoverInfoConsts";
|
import TrackHoverInfoPopupTable from "./TrackHoverInfoTable";
|
||||||
|
|
||||||
export default function TrackHoverInfoPopup(props) {
|
export default function TrackHoverInfoPopup({track}) {
|
||||||
const { track } = props;
|
|
||||||
|
|
||||||
const slopeValue = Array.from(new Set([
|
|
||||||
Math.abs(track.start_slope).toFixed(1),
|
|
||||||
Math.abs(track.end_slope).toFixed(1),
|
|
||||||
]));
|
|
||||||
const slopeOptions = {join: ' / ', subOptions: {suffix: ' ‰'}};
|
|
||||||
const showAlignHint = track.shape === TrackShape.STRAIGHT;
|
const showAlignHint = track.shape === TrackShape.STRAIGHT;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="track-hover-info-popup">
|
<div className="track-hover-info-popup">
|
||||||
<table>
|
<TrackHoverInfoPopupTable track={track} />
|
||||||
<tbody>
|
|
||||||
{Constants.map.showDebugTrackIds && (
|
|
||||||
<InfoPopupItem icon={HashIcon} value={track.id} label="Track ID" />
|
|
||||||
)}
|
|
||||||
<InfoPopupItem icon={HashIcon} value={track['id_isolation']} label="Isolation ID" />
|
|
||||||
<InfoPopupItem icon={SpeedIcon} value={track['derailspeed']} label="Derail speed" options={{suffix: ' km/h'}} />
|
|
||||||
<InfoPopupItem icon={SpeedIcon} value={track['maxspeed']} label="Max. speed" options={{suffix: ' km/h'}} />
|
|
||||||
<InfoPopupItem icon={LightningIcon} value={track['electrificationStatus']} label="Electrification" options={{translationConstKey: 'electrification'}} />
|
|
||||||
<InfoPopupItem icon={QuestionMarkIcon} value={track.shape} label="Type" options={{join: ' / ', translationConstKey: 'shape'}} />
|
|
||||||
<InfoPopupItem icon={SlopeIcon} value={slopeValue} label="Slope" options={slopeOptions} />
|
|
||||||
<InfoPopupSwitchItems track={track} />
|
|
||||||
<InfoPopupShapeItems track={track} />
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
{showAlignHint && (
|
{showAlignHint && (
|
||||||
<div className="track-hover-info-popup__align">
|
<div className="track-hover-info-popup__align">
|
||||||
Double click to align the view
|
Double click to align the view
|
||||||
@ -38,58 +15,3 @@ export default function TrackHoverInfoPopup(props) {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InfoPopupSwitchItems(props) {
|
|
||||||
const { track } = props;
|
|
||||||
if (!track.switch) return null;
|
|
||||||
|
|
||||||
return <>
|
|
||||||
<InfoPopupItem icon={HashIcon} value={track.switch.id_switch} label="Switch ID" />
|
|
||||||
<InfoPopupItem icon={QuestionMarkIcon} value={track.switch.bare_model} label="Switch model" />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
|
|
||||||
function InfoPopupShapeItems(props) {
|
|
||||||
const { track } = props;
|
|
||||||
if (track.r === 0) return null;
|
|
||||||
|
|
||||||
return <InfoPopupItem icon={RadiusIcon} value={Math.abs(track.r)} label="Radius" options={{suffix: ' m', precision: 0}} />
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (typeof value === 'number' && 'precision' in options) {
|
|
||||||
value = value.toFixed(options.precision);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === undefined || value === null || value === '') {
|
|
||||||
return "N/A";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `${value}${suffix || ''}`.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function InfoPopupItem({ icon: Icon, label, value, options = {} }) {
|
|
||||||
const textValue = processItemValue(value, options);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
<div className="track-hover-info-popup-label">
|
|
||||||
{Icon ? <Icon width={15} height={15} color="#888" strokeWidth={3} /> : null}
|
|
||||||
{label}
|
|
||||||
</div>
|
|
||||||
</th>
|
|
||||||
<td>{textValue}</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
82
src/components/map/track-hover-info/TrackHoverInfoTable.jsx
Normal file
82
src/components/map/track-hover-info/TrackHoverInfoTable.jsx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import Constants from "../../../helpers/constants";
|
||||||
|
import { QuestionMarkIcon, SlopeIcon, SpeedIcon, HashIcon, LightningIcon, RadiusIcon } from "../../../icons";
|
||||||
|
import TrackHoverInfoConsts from "./TrackHoverInfoConsts";
|
||||||
|
|
||||||
|
export default function TrackHoverInfoPopupTable({track}) {
|
||||||
|
const slopeValue = Array.from(new Set([
|
||||||
|
Math.abs(track.start_slope).toFixed(1),
|
||||||
|
Math.abs(track.end_slope).toFixed(1),
|
||||||
|
]));
|
||||||
|
const slopeOptions = {join: ' / ', subOptions: {suffix: ' ‰'}};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<table className="track-hover-info-popup-table">
|
||||||
|
<tbody>
|
||||||
|
{Constants.map.showDebugTrackIds && (
|
||||||
|
<InfoPopupItem icon={HashIcon} value={track.id} label="Track ID" />
|
||||||
|
)}
|
||||||
|
<InfoPopupItem icon={HashIcon} value={track['id_isolation']} label="Isolation ID" />
|
||||||
|
<InfoPopupItem icon={SpeedIcon} value={track['derailspeed']} label="Derail speed" options={{suffix: ' km/h'}} />
|
||||||
|
<InfoPopupItem icon={SpeedIcon} value={track['maxspeed']} label="Max. speed" options={{suffix: ' km/h'}} />
|
||||||
|
<InfoPopupItem icon={LightningIcon} value={track['electrificationStatus']} label="Electrification" options={{translationConstKey: 'electrification'}} />
|
||||||
|
<InfoPopupItem icon={QuestionMarkIcon} value={track.shape} label="Type" options={{join: ' / ', translationConstKey: 'shape'}} />
|
||||||
|
<InfoPopupItem icon={SlopeIcon} value={slopeValue} label="Slope" options={slopeOptions} />
|
||||||
|
<InfoPopupSwitchItems track={track} />
|
||||||
|
<InfoPopupShapeItems track={track} />
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function InfoPopupSwitchItems({track}) {
|
||||||
|
if (!track.switch) return null;
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<InfoPopupItem icon={HashIcon} value={track.switch.id_switch} label="Switch ID" />
|
||||||
|
<InfoPopupItem icon={QuestionMarkIcon} value={track.switch.bare_model} label="Switch model" />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
|
||||||
|
function InfoPopupShapeItems({track}) {
|
||||||
|
if (track.r === 0) return null;
|
||||||
|
|
||||||
|
return <InfoPopupItem icon={RadiusIcon} value={Math.abs(track.r)} label="Radius" options={{suffix: ' m', precision: 0}} />
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (typeof value === 'number' && 'precision' in options) {
|
||||||
|
value = value.toFixed(options.precision);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return "N/A";
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${value}${suffix || ''}`.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function InfoPopupItem({ icon: Icon, label, value, options = {} }) {
|
||||||
|
const textValue = processItemValue(value, options);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
<th>
|
||||||
|
<div className="track-hover-info-popup-label">
|
||||||
|
{Icon ? <Icon color={undefined} width={15} height={15} strokeWidth={3} /> : null}
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>{textValue}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user