Extract TrackHoverInfoTable component
- Extracted TrackHoverInfoTable as a separate component
This commit is contained in:
parent
1d86f0f42f
commit
daa86d7b85
@ -12,16 +12,16 @@
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.track-hover-info-popup svg {
|
||||
.track-hover-info-popup-table svg {
|
||||
width: 1.2em;
|
||||
height: 1.2em;
|
||||
}
|
||||
|
||||
.track-hover-info-popup table {
|
||||
.track-hover-info-popup-table {
|
||||
border-spacing: 2px;
|
||||
}
|
||||
|
||||
.track-hover-info-popup th {
|
||||
.track-hover-info-popup-table th {
|
||||
text-align: start;
|
||||
padding-right: 2em;
|
||||
color: var(--text-color-disabled);
|
||||
@ -33,7 +33,7 @@
|
||||
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;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { useState, useEffect, useCallback, useRef, useContext } from "react";
|
||||
import { hoveredTrack$ } from "../../../services/trackHoverInfoService";
|
||||
import { useState, useEffect, useCallback, useRef, useContext, useMemo } from "react";
|
||||
import { hoveredTrack$, touchClickedTrack$ } from "../../../services/trackHoverInfoService";
|
||||
import TrackHoverInfoPopup from "./TrackHoverInfoPopup";
|
||||
import SettingsContext from "../../../contexts/SettingsContext";
|
||||
import { showDialog } from "../../../services/dialogService";
|
||||
import './TrackHoverInfo.css';
|
||||
import TrackHoverInfoPopupTable from "./TrackHoverInfoTable";
|
||||
|
||||
export default function TrackHoverInfo() {
|
||||
const { showTrackHoverInfo } = useContext(SettingsContext);
|
||||
@ -18,6 +20,7 @@ function InnerTrackHoverInfo() {
|
||||
const [ hoveredTrack, setHoveredTrack ] = useState(null);
|
||||
const mousePos = useRef({ x: 0, y: 0 });
|
||||
const divRef = useRef(null);
|
||||
const isMobile = useMemo(() => window.matchMedia('(pointer: coarse)').matches, []);
|
||||
|
||||
const handleMouseMove = useCallback((event) => {
|
||||
mousePos.current.x = event.clientX;
|
||||
@ -26,19 +29,34 @@ function InnerTrackHoverInfo() {
|
||||
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);
|
||||
const hoveredTrackSub = hoveredTrack$.subscribe(track => {
|
||||
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 () => {
|
||||
subscription.unsubscribe();
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
}
|
||||
hoveredTrackSub.unsubscribe();
|
||||
touchClickedTrackSub.unsubscribe();
|
||||
if (!isMobile) {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
}
|
||||
};
|
||||
}, [handleMouseMove]);
|
||||
|
||||
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 TrackHoverInfoConsts from "./TrackHoverInfoConsts";
|
||||
import TrackHoverInfoPopupTable from "./TrackHoverInfoTable";
|
||||
|
||||
export default function TrackHoverInfoPopup(props) {
|
||||
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: ' ‰'}};
|
||||
export default function TrackHoverInfoPopup({track}) {
|
||||
const showAlignHint = track.shape === TrackShape.STRAIGHT;
|
||||
|
||||
return (
|
||||
<div 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>
|
||||
<TrackHoverInfoPopupTable track={track} />
|
||||
{showAlignHint && (
|
||||
<div className="track-hover-info-popup__align">
|
||||
Double click to align the view
|
||||
@ -38,58 +15,3 @@ export default function TrackHoverInfoPopup(props) {
|
||||
</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