From daa86d7b85fcc8eef9257f632db8ca34a0bf7681 Mon Sep 17 00:00:00 2001 From: izawartka <59137928+izawartka@users.noreply.github.com> Date: Thu, 13 Aug 2026 19:02:15 +0200 Subject: [PATCH] Extract TrackHoverInfoTable component - Extracted TrackHoverInfoTable as a separate component --- .../map/track-hover-info/TrackHoverInfo.css | 8 +- .../map/track-hover-info/TrackHoverInfo.jsx | 36 ++++++-- .../track-hover-info/TrackHoverInfoPopup.jsx | 84 +------------------ .../track-hover-info/TrackHoverInfoTable.jsx | 82 ++++++++++++++++++ 4 files changed, 116 insertions(+), 94 deletions(-) create mode 100644 src/components/map/track-hover-info/TrackHoverInfoTable.jsx diff --git a/src/components/map/track-hover-info/TrackHoverInfo.css b/src/components/map/track-hover-info/TrackHoverInfo.css index b930e31..b6e71d8 100644 --- a/src/components/map/track-hover-info/TrackHoverInfo.css +++ b/src/components/map/track-hover-info/TrackHoverInfo.css @@ -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; diff --git a/src/components/map/track-hover-info/TrackHoverInfo.jsx b/src/components/map/track-hover-info/TrackHoverInfo.jsx index eb215f6..2b858fa 100644 --- a/src/components/map/track-hover-info/TrackHoverInfo.jsx +++ b/src/components/map/track-hover-info/TrackHoverInfo.jsx @@ -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( + , + [{ 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; diff --git a/src/components/map/track-hover-info/TrackHoverInfoPopup.jsx b/src/components/map/track-hover-info/TrackHoverInfoPopup.jsx index 80ff36f..990d08e 100644 --- a/src/components/map/track-hover-info/TrackHoverInfoPopup.jsx +++ b/src/components/map/track-hover-info/TrackHoverInfoPopup.jsx @@ -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 (
- - - {Constants.map.showDebugTrackIds && ( - - )} - - - - - - - - - -
+ {showAlignHint && (
Double click to align the view @@ -38,58 +15,3 @@ export default function TrackHoverInfoPopup(props) {
); } - -function InfoPopupSwitchItems(props) { - const { track } = props; - if (!track.switch) return null; - - return <> - - - -} - -function InfoPopupShapeItems(props) { - const { track } = props; - if (track.r === 0) return null; - - return -} - -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 ( - - -
- {Icon ? : null} - {label} -
- - {textValue} - - ); -} diff --git a/src/components/map/track-hover-info/TrackHoverInfoTable.jsx b/src/components/map/track-hover-info/TrackHoverInfoTable.jsx new file mode 100644 index 0000000..62c177c --- /dev/null +++ b/src/components/map/track-hover-info/TrackHoverInfoTable.jsx @@ -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 ( + + + {Constants.map.showDebugTrackIds && ( + + )} + + + + + + + + + +
+ ); +} + +function InfoPopupSwitchItems({track}) { + if (!track.switch) return null; + + return <> + + + +} + +function InfoPopupShapeItems({track}) { + if (track.r === 0) return null; + + return +} + +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 ( + + +
+ {Icon ? : null} + {label} +
+ + {textValue} + + ); +}